Go (Golang) Tutorial 0/45 lessons ~6 min read Lesson 14

    Maps

    Maps are Go's hash table — unordered key-value collections.

    Course progress0%
    Focus
    10 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Maps are Go's hash table — unordered key-value collections. Keys must be comparable (no slices, maps, or funcs as keys). Maps are reference types: passing a map to a function allows the callee to mutate the same underlying data.

    Maps are not safe for concurrent access without sync.Mutex or sync.Map. The comma-ok idiom distinguishes missing keys from zero values. Understanding nil map behavior (reads OK, writes panic) prevents production crashes.

    Maps appear in config parsing, in-memory caches, HTTP header lookups, and JSON object deserialization — this lesson covers creation, iteration, deletion, and concurrency patterns.

    The story

    An API gateway caches JWT validation results keyed by token fingerprint — a map[string]Claims gives O(1) lookups under high request rates. At Uber, in-memory maps back rate-limit counters per rider ID, while Redis maps extend the same pattern across distributed edge nodes.

    Maps are reference types: passing them to functions shares the same underlying hash table, so concurrent access requires synchronization (mutex or sync.Map) in production servers.

    Understanding the topic

    Key concepts

    • m := make(map[string]int) or map[string]int{} — latter creates empty non-nil map.
    • var m map[string]int is nil — read returns zero, write panics.
    • Comma-ok: val, ok := m[key]; delete(m, key) removes entry.
    • Iteration order is randomized — don't depend on order.
    • Maps not concurrent-safe — use sync.Map or mutex wrapper.
    • maps package (Go 1.21+): Keys, Values, Clone, Equal helpers.

    Step-by-step explanation

    1. make allocates internal hash table; literal {} also initializes.
    2. Insert/update: m[key] = value.
    3. Lookup: val := m[key] — zero value if missing.
    4. delete removes key; no error if key absent.
    5. len(m) is element count; no cap concept.
    6. Range over map gives key, value in random order.

    Practical code example

    In-memory rate limiter map with comma-ok and safe initialization:

    go
    package main
    import (
    "fmt"
    "maps"
    )
    type RateLimiter struct {
    counts map[string]int
    limit int
    }
    func NewRateLimiter(limit int) *RateLimiter {
    return &RateLimiter{counts: make(map[string]int), limit: limit}
    }
    func (r *RateLimiter) Allow(clientID string) bool {
    count, _ := r.counts[clientID]
    if count >= r.limit {
    return false
    }
    r.counts[clientID] = count + 1
    return true
    }
    func main() {
    rl := NewRateLimiter(2)
    fmt.Println(rl.Allow("client-a"), rl.Allow("client-a"), rl.Allow("client-a"))
    snapshot := maps.Clone(rl.counts)
    fmt.Println("snapshot:", snapshot)
    }

    Line-by-line code explanation

    • cache := make(map[string]Claims) creates an empty map ready for insertion.
    • cache[token] = claims stores a value — creates the key if absent.
    • claims, ok := cache[token] reads with a boolean indicating whether the key existed.
    • if !ok { ... } handles cache misses without confusing zero values for real data.
    • delete(cache, token) removes an entry — safe even if the key is missing.
    • for k, v := range cache iterates entries — order is intentionally randomized.
    • len(cache) returns the number of key-value pairs currently stored.
    • var m map[string]int nil map reads return zero but writes panic — always initialize with make.

    Key takeaway: Always make() or literal before writes. Clone map before exposing internal state. For concurrent use, wrap with sync.Mutex.

    Real-world use

    Where you'll use this in production

    • In-memory caches with TTL wrappers (sync.Map or ristretto).
    • HTTP middleware rate limiting per client IP.
    • JSON object decoding into map[string]any for dynamic payloads.
    • Graph adjacency lists and lookup tables in algorithms.

    Best practices

    • Initialize with make or {} before any write.
    • Use comma-ok for existence checks, not zero-value comparison.
    • Protect concurrent maps with mutex or sync.Map.
    • Clone before returning internal map from struct methods.
    • Use struct{} as value for set semantics: map[string]struct{}.
    • Consider sync.Map for read-heavy concurrent caches.

    Common mistakes

    • Writing to nil map — panic.
    • Ranging and deleting same map — safe in Go but be deliberate.
    • Using float64 as map key — NaN != NaN breaks lookups.
    • Assuming iteration order for tests — flaky tests result.
    • Returning internal map without copy — caller can mutate state.

    Advanced interview questions

    Q1BeginnerCan you use slice as map key?
    No — keys must be comparable; slices, maps, funcs are not.
    Q2Beginnernil map read vs write?
    Read returns zero value; write panics — must make first.
    Q3IntermediateMap iteration order?
    Randomized intentionally — prevents reliance on order.
    Q4Intermediatesync.Map vs map+Mutex?
    sync.Map for read-heavy, many keys, stable set; mutex+map simpler for most cases.
    Q5AdvancedImplement thread-safe cache with TTL in Go.
    map+Mutex or sync.Map; background goroutine sweeps expired; or use ristretto/bigcache library.

    Summary

    Maps are reference-type hash tables; initialize before writing. Comma-ok distinguishes missing keys from zero values. Not concurrent-safe — use sync.Mutex or sync.Map. Iteration order is random — by design. Next lesson: structs — grouping related data.

    Ready to mark this lesson complete?Track your journey across the entire course.