Go Maps Iteration Order
Why Go map iteration is intentionally randomized by the runtime, and how to write deterministic key iteration.
The Go language specification is explicit about map iteration:
The iteration order over maps is not specified and is not guaranteed to be the same from one iteration to the next.
This is not just a disclaimer in the specification. The Go runtime actively enforces non-deterministic iteration to prevent programs from depending on hash table layout.
The Deliberate Randomization in mapiterinit
When you write for k, v := range m, the compiler lowers this to a call to runtime.mapiterinit. Inside the runtime, Go picks a randomized starting bucket and a randomized slot offset within that bucket:
// From Go runtime/map.go (traditional map implementation):
func mapiterinit(t *maptype, h *hmap, it *hiter) {
// ...
// decide where to start
r := uintptr(fastrandn(uint32(1 << h.B)))
it.startBucket = r
it.offset = uint8(fastrandn(abi.MapBucketCount))
// ...
}sequenceDiagram
participant User as for k, v := range m
participant Runtime as runtime.mapiterinit
participant Buckets as Hash Buckets
User->>Runtime: Initialize iterator
Runtime->>Runtime: fastrandn() -> startBucket
Runtime->>Runtime: fastrandn() -> slotOffset
Runtime->>Buckets: Begin iteration from (startBucket, slotOffset)
Buckets-->>User: Keys yielded in randomized sequenceBecause fastrandn produces a new starting point on every loop invocation, two consecutive for ... range loops over the exact same map in the same process can yield keys in completely different sequences.
In modern Go runtimes using Swiss Tables, the same principle holds: the iterator seeds a random group and slot offset before probing control bytes.
Why the Runtime Does This
Before Go 1.0, map iteration walked buckets sequentially from bucket 0. The order was not technically guaranteed, but it was deterministic for identical insertion patterns.
This created a widespread failure mode: developers wrote unit tests that accidentally relied on hash layout order. When the hash seed changed, when map capacity resized, or when code ran on a different CPU architecture, tests failed unpredictably across environments.
The Go authors added deliberate iteration randomization so that code depending on map order fails immediately and visibly in local development rather than in production.
graph TD
A["Deterministic hash iteration (pre-Go 1.0)"] --> B["Tests accidentally rely on hash layout"]
B --> C["Silent breakages on map resize or architecture changes"]
D["Runtime randomization (mapiterinit fastrandn)"] --> E["Order dependencies break immediately in dev"]
E --> F["Resilient, deterministic application code"]
class A,B diag-warning;
class C diag-error;
class D,E diag-ingress;
class F diag-success;How to Iterate Deterministically
When you require stable iteration (for example, generating JSON payloads, writing deterministic serialization formats, or creating reproducible test assertions), extract the keys, sort them, and iterate over the sorted slice:
package main
import (
"fmt"
"slices"
)
func main() {
counts := map[string]int{
"amber": 12,
"cobalt": 4,
"bronze": 9,
"zinc": 1,
}
// Allocate exact capacity to avoid slice reallocation
keys := make([]string, 0, len(counts))
for k := range counts {
keys = append(keys, k)
}
// Sort keys in place with slices.Sort
slices.Sort(keys)
for _, k := range keys {
fmt.Printf("%s: %d\n", k, counts[k])
}
}In Go 1.23 and later, you can also collect keys using the standard maps.Keys iterator helper:
keys := slices.Collect(maps.Keys(counts))
slices.Sort(keys)Implementation Rules
- Never assert on map output directly in tests: Comparing
fmt.Sprint(m)against an expected string will produce flaky tests. Usereflect.DeepEqualorgoogle/go-cmpfor map equality. - Pre-allocate key slices: When collecting map keys for sorting, pass
len(m)as slice capacity (make([]K, 0, len(m))) to eliminate heap reallocations during collection. - Map hashing is per-process seeded: The runtime initializes a global hash seed at startup via AES-NI / hardware entropy. You cannot reproduce map hash distributions across separate program runs.