9.5 Iteration Order: Randomized by Design
Right, let’s talk about one of the first things that will make you slam your desk and question your sanity when working with Go maps: iteration order. Or, more accurately, the lack of a guaranteed one.
If you come from a language like Python or Java, you might be under the impression that a map, when iterated, will give you back its keys in the order you inserted them. That is a comforting, orderly lie. In Go, it’s a flat-out fantasy. The language designers decided that your desire for order was a crutch you didn’t need and, more importantly, a promise that would make the implementation slower. So they took it away.
Here’s the canonical example that every Go developer has run into at least once. You make a little map, you put some things in it in a very specific order, and then you ask for them back.
myMap := map[string]int{
"alpha": 1,
"beta": 2,
"gamma": 3,
}
fmt.Println("Keys in map:")
for key := range myMap {
fmt.Println(key)
}
Run this. Go on, I’ll wait. Did you get alpha, beta, gamma? Run it again. And again. On my machine, the first run gave me gamma, alpha, beta. The second gave me beta, gamma, alpha. It’s a slot machine where every pull is a loser if you’re expecting consistency.
Why This Madness Exists
This isn’t a bug; it’s a meticulously crafted feature. The Go runtime intentionally randomizes the starting point for map iteration. They’ve been doing it since Go 1.0, and they made the randomization even more aggressive in Go 1.12 to prevent a specific class of bugs.
The reason is twofold, and both are brilliant in their own ruthless way:
To Keep Us Honest: The primary goal is to force developers to never, ever rely on the order of keys in a map. If the order is different every single time you run your program, you can’t accidentally write code that works in your test environment (where you might get “lucky” with a consistent order) but breaks catastrophically in production. It’s the programming equivalent of tough love. The compiler is your brilliant but brutally honest friend saying, “You think you need order? You don’t. And I’m not going to let you pretend you do.”
Performance & Security: Maps are implemented as hash tables. The obvious, “naive” way to iterate would be to start at bucket 0 and go to bucket N. However, if an attacker knows your hash function and your map structure, they could potentially create a set of keys that all hash to the same bucket, leading to worst-case performance (a denial-of-service attack). By randomizing the starting bucket and the iteration order within buckets, Go makes the performance of your program much more predictable and immune to these kinds of attacks. It’s a defensive design.
The Official Way to Get Order: Sort the Keys Yourself
So, you need order? The language isn’t going to give it to you for free. You have to be explicit about it. The standard pattern is to:
- Extract all the keys into a slice.
- Sort that slice using the
sortpackage. - Iterate over the sorted slice and use each key to access the value in the map.
import "sort"
myMap := map[string]int{
"zulu": 26,
"alpha": 1,
"bravo": 2,
}
// 1. Create a slice for the keys
keys := make([]string, 0, len(myMap))
for k := range myMap {
keys = append(keys, k)
}
// 2. Sort the slice
sort.Strings(keys) // For string keys
// 3. Iterate over the sorted slice to get map values in order
for _, key := range keys {
fmt.Printf("%s: %d\n", key, myMap[key])
}
// Output will always be:
// alpha: 1
// bravo: 2
// zulu: 26
This is verbose, yes. But it’s also utterly unambiguous and completely in your control. You’re not relying on any hidden behavior.
The One (Semi-)Exception: Range over Maps in Tests
There’s a subtle edge case that can bite you if you’re using go test and its golden -count=1 flag. The map iteration seed is set once per program execution. If your test function runs multiple times in the same process and you’re using range over a map to, say, build a string for an error message, the first test run might have one order and the second run might have another, causing a spurious failure.
The solution is either to sort the keys as shown above before building your test output, or to run your tests with go test -count=1, which prevents test result caching and ensures each test runs in a fresh process, resetting the random seed.
The takeaway is simple but absolute: Treat the for range myMap loop as a black box that gives you every key exactly once, but in a order that is intentionally useless to you. Any time you care about the sequence, you must impose your own order. It feels like a hassle at first, but it’s a discipline that writes more robust, secure, and predictable code. And honestly, after a while, you’ll start to appreciate the brutal honesty of it all.