Iterating over map in Go: how to avoid pitfalls (and not lose data)
\n\nIf you've ever written in Go, you've certainly encountered map — one of the most powerful and treacherous data structures. It seems simple enough: take range, iterate over keys, get values. But the devil, as always, is in the details. The question "Iterating over all the keys of a map" on Stack Overflow has gathered thousands of views, and for good reason.
Today we'll break down the top mistakes when iterating over a map in Go that even experienced developers make. You'll learn why the iteration order is a lottery, how to safely work with a map in goroutines, and what to do if a map suddenly turns out to be nil. Let's go!
Mistake #1: Expecting order (or "Where are my keys?")
\n\nThe most common trap is the belief that range will return keys in the same order you added them. In Go, this is not the case. The iteration order over a map is fundamentally non-deterministic.
Look at this code:
\n\npackage main\n\nimport "fmt"\n\nfunc main() {\n m := map[string]int{\n "apple": 1,\n "banana": 2,\n "cherry": 3,\n }\n\n for i := 0; i < 3; i++ {\n fmt.Println("Iteration", i+1)\n for k, v := range m {\n fmt.Printf(" %s: %d\\n", k, v)\n }\n }\n}\n\n\nMost likely, each run will produce a different order. This is not a bug, but a feature: Go deliberately randomizes the order so that developers don't get used to it and don't build logic based on a random sequence.
\n\nHow to avoid it?
\n- \n
- Don't rely on order — this is the main rule. \n
- If order is important, use
[]stringto store keys separately and sort them: \n
package main\n\nimport (\n "fmt"\n "sort"\n)\n\nfunc main() {\n m := map[string]int{"apple": 1, "banana": 2, "cherry": 3}\n\n keys := make([]string, 0, len(m))\n for k := range m {\n keys = append(keys, k)\n }\n sort.Strings(keys)\n\n for _, k := range keys {\n fmt.Printf("%s: %d\\n", k, m[k])\n }\n}\n\n\nTip: If you need to preserve insertion order, consider the library github.com/elliotchance/orderedmap or write your own wrapper based on two structures (map + slice).
Mistake #2: Iterating while writing simultaneously (data race)
\n\nIn Go, map is not safe for concurrent access. If one goroutine reads (iterates) a map while another writes to it at the same time, you'll get a fatal error: concurrent map iteration and map write.
\n\nExample of a fatal error:
\n\npackage main\n\nimport (\n "fmt"\n "time"\n)\n\nfunc main() {\n m := make(map[int]int)\n\n go func() {\n for i := 0; i < 100; i++ {\n m[i] = i\n }\n }()\n\n go func() {\n for k, v := range m {\n fmt.Println(k, v)\n }\n }()\n\n time.Sleep(time.Second)\n}\n\n\nThis code will crash with a panic. And not always — the race condition may only appear on the 1000th run.
\n\nHow to avoid it?
\n\nThere are three proven methods:
\n\n- \n
- sync.Mutex — a classic mutex for synchronizing access: \n
package main\n\nimport (\n "fmt"\n "sync"\n)\n\ntype SafeMap struct {\n mu sync.Mutex\n m map[int]int\n}\n\nfunc (s *SafeMap) Set(key, value int) {\n s.mu.Lock()\n defer s.mu.Unlock()\n s.m[key] = value\n}\n\nfunc (s *SafeMap) Iterate(f func(key, value int) bool) {\n s.mu.Lock()\n defer s.mu.Unlock()\n for k, v := range s.m {\n if !f(k, v) {\n break\n }\n }\n}\n\nfunc main() {\n sm := SafeMap{m: make(map[int]int)}\n sm.Set(1, 10)\n sm.Set(2, 20)\n\n sm.Iterate(func(k, v int) b