How do maps work in Go, and what are common gotchas?
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.