Maps

intermediate go maps concurrency

A map is Go’s built-in key-value store. Think of it like a dictionary or hash table — we look things up by a key instead of an index.

We create one with make, or with a literal.

ages := make(map[string]int)   // empty map, keys are strings, values are ints
ages["manish"] = 27            // set
fmt.Println(ages["manish"])    // get → 27

scores := map[string]int{      // map literal
    "math": 90,
    "cs":   95,
}

The comma-ok idiom

Here’s the trap. If we read a key that doesn’t exist, Go does NOT error. It returns the zero value of the value type.

fmt.Println(ages["nobody"]) // 0 — but did we store 0, or is the key missing?

We can’t tell the difference from the value alone. So Go gives us a second return value — a boolean that says “yes, this key actually exists.” This is the comma-ok idiom.

v, ok := ages["nobody"]
fmt.Println(v, ok)  // 0 false — the key is missing
v2, ok2 := ages["manish"]
fmt.Println(v2, ok2) // 27 true — the key exists

The only difference between the two reads is that second value. We use it any time a zero value is ambiguous.

Deleting

We remove a key with the built-in delete. Deleting a key that isn’t there is a no-op — no panic, nothing.

delete(ages, "manish")  // removes the key
delete(ages, "ghost")   // safe even if missing

Maps are reference-like

In simple language, when we pass a map to a function, we’re not copying it. We’re passing a reference to the same underlying data. So a function can mutate our map.

func addOne(m map[string]int) {
    m["count"]++   // this affects the caller's map too
}

This is the opposite of arrays, which copy. It’s because internally a map value is a pointer to the real hash table.

Iteration order is random (gotcha)

This one shows up in interviews. Go intentionally randomizes map iteration order. Run the same loop twice and the keys may come out in a different order.

for k, v := range scores {
    fmt.Println(k, v)   // order is NOT guaranteed, changes between runs
}

Why? So we don’t accidentally write code that depends on order. If we need sorted output, we collect the keys into a slice and sort them.

Maps are NOT safe for concurrent writes

Big one. If two goroutines write to the same map at the same time, Go will crash the program with a “concurrent map writes” panic. It’s a hard, intentional crash.

For concurrent access we either guard the map with a sync.Mutex, or use sync.Map for certain read-heavy patterns.

var mu sync.Mutex
counts := map[string]int{}

// inside a goroutine:
mu.Lock()
counts["hits"]++
mu.Unlock()

nil map: reads OK, writes panic

A map declared but never initialized is nil. Reading from a nil map is fine — we just get zero values. But writing to a nil map panics.

var m map[string]int   // nil map (no make)
fmt.Println(m["x"])    // 0 — reading is fine
m["x"] = 1             // PANIC: assignment to entry in nil map

So we always make a map before writing to it.

Interview soundbite

Maps return the zero value for missing keys, so we use the comma-ok idiom v, ok := m[k] to tell “missing” from “zero.” Iteration order is deliberately randomized, maps are reference-like so functions can mutate them, and they are not safe for concurrent writes — concurrent writes panic, so we reach for a mutex or sync.Map. Reading a nil map is fine but writing to one panics.