7. Maps: internals and idioms
7.1 Declaration and the comma-ok idiom
m := make(map[string]int)
m["a"] = 1
v, ok := m["b"] // ok=false, v=0 (the zero value) — key absent, not an error
if ok {
use(v)
}
delete(m, "a") // no-op if key absent, never panics
Reading a missing key never panics — you get the zero value. This is a
frequent bug source for beginners who forget to check ok and silently
treat "absent" as "present with value 0."
7.2 Nil maps: readable, not writable
var m map[string]int // nil, not an empty map
v := m["x"] // fine — returns 0
m["x"] = 1 // PANIC: assignment to entry in nil map
Always initialize with make (or a composite literal) before writing.
7.3 Iteration order is intentionally randomized
Go's map iteration order is randomized by design, specifically to stop anyone from depending on it. If you need sorted output, collect keys into a slice and sort them:
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Println(k, m[k])
}
7.4 Maps are not safe for concurrent use
A concurrent map write (or a write racing a read) panics with fatal error: concurrent map read and map write — Go detects this at runtime
rather than silently corrupting the map. Guard with a sync.RWMutex
(Chapter 17) or use sync.Map for specific high-read/rare-write patterns.
7.5 Structs, not strings, as keys
Any comparable type can be a map key — including structs — which gives you composite keys without string concatenation:
type CacheKey struct {
UserID int
Region string
}
cache := make(map[CacheKey]Result)
cache[CacheKey{UserID: 42, Region: "us-east"}] = result
7.6 Real-world example
Day 4 of the 90-day plan's ring-buffer request log uses exactly this
pattern: a slice for the fixed-size ring buffer plus a map[string][]int
indexing IPs to positions in the ring, giving O(1) "show me the last N
requests from this IP" without scanning the whole buffer.