Iterating over map in Go: 5 fatal mistakes and how to avoid them

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Iterating over map in Go: how to avoid pitfalls (and not lose data)

\n\n

If 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.

\n\n

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!

\n\n

Mistake #1: Expecting order (or "Where are my keys?")

\n\n

The 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.

\n\n

Look at this code:

\n\n
package 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\n

Most 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\n

How to avoid it?

\n
    \n
  • Don't rely on order — this is the main rule.
  • \n
  • If order is important, use []string to store keys separately and sort them:
  • \n
\n\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\n

Tip: 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).

\n\n

Mistake #2: Iterating while writing simultaneously (data race)

\n\n

In 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\n

Example of a fatal error:

\n\n
package 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\n

This code will crash with a panic. And not always — the race condition may only appear on the 1000th run.

\n\n

How to avoid it?

\n\n

There are three proven methods:

\n\n
    \n
  1. sync.Mutex — a classic mutex for synchronizing access:
  2. \n
\n\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                

Blogs

Book Recommendations