How do maps work in Go, and what are common gotchas?

5 minintermediategomapsconcurrencyfundamentals

Quick Answer

A Go map[K]V is a hash table. Keys must be a comparable type (no slices, maps, or funcs as keys). Maps are reference types under the hood, so passing one to a function doesn't copy it — mutations are visible to the caller. Common gotchas: a nil map panics on write but not on read; iteration order is intentionally randomized by the runtime, so never rely on it for deterministic output; maps are not safe for concurrent read/write from multiple goroutines and will panic with "concurrent map writes" if you try — use a sync.Mutex or sync.Map instead; and you can't take the address of a map value (&m["x"] is a compile error) because entries can move during rehashing.

Detailed Answer

Maps look simple, but four specific behaviors trip up almost everyone new to Go.

Checking for existence vs. zero value

m := map[string]int{"a": 1}
v := m["b"]          // 0 — can't tell "missing" from "present with value 0"
v, ok := m["b"]      // v=0, ok=false — this is the idiomatic check
if ok {
    fmt.Println("found:", v)
}

Randomized iteration order

for k, v := range m {
    fmt.Println(k, v)  // order changes between runs, by design
}

Go randomizes map iteration order deliberately, to stop anyone depending on it. If you need sorted output, collect the keys and sort them yourself:

keys := make([]string, 0, len(m))
for k := range m {
    keys = append(keys, k)
}
sort.Strings(keys)

Concurrent access panics

// Running this with -race, or under real concurrent load, panics:
// "fatal error: concurrent map writes"
go func() { m["x"] = 1 }()
go func() { m["y"] = 2 }()

Maps have zero built-in concurrency safety. Guard shared maps with a sync.Mutex, or use sync.Map for specific high-read, low-write patterns.

No addressable map values

type Point struct{ X, Y int }
m := map[string]Point{"origin": {0, 0}}
m["origin"].X = 5   // compile error: cannot assign to struct field

Map values aren't addressable, since the runtime may relocate them during a rehash. Fix it by reassigning the whole value, or storing pointers (map[string]*Point) instead.

Related Resources