Why does ranging over the same Go map twice give the entries in a different order?
answer
- order was never part of the deal
- the runtime picks where to start
- a random starting point per loop
- deliberate, to break order dependence
- sort the keys when you need stability
basics
~20 sGo's runtime starts every map iteration at a randomly chosen position, so map order is undefined and changes from loop to loop. For stable output, copy the keys into a slice, sort it, and index the map in that order.
solid answer
~40 sA Go map is a hash table, and the language specification says the iteration order over a map is unspecified. The runtime goes further: it deliberately picks a random starting point in the table for each `for range` over a map, so two loops over the same unmodified map can hand back different orders, and so can two runs of the same binary. That is a feature rather than an accident — it stops code from quietly depending on an order the implementation never promised and that would change anyway as the map grows. The only guarantee is that every entry present for the whole loop is produced exactly once; entries added while you iterate may or may not show up. When you need determinism, sort the keys: `for _, k := range slices.Sorted(maps.Keys(m))`.
code
go · 11 linescounts := map[string]int{"a": 1, "b": 2, "c": 3}
// Unordered: the sequence differs between loops and between runs.
for k, v := range counts {
fmt.Println(k, v)
}
// Deterministic: a, b, c on every run.
for _, k := range slices.Sorted(maps.Keys(counts)) {
fmt.Println(k, counts[k])
}go deeper
Be ready to say plainly that map iteration order in Go is undefined and randomised by the runtime, and to show the sort-the-keys workaround without hesitating.
Explain that the runtime chooses a random starting point in the table for each loop, and state exactly what is guaranteed when entries are added or deleted while you iterate.
Show where undefined order bites in production — non-reproducible output files, flaky golden tests, unstable log lines — and make the output deterministic at the boundary rather than hoping the order holds.
Treat it as an API decision: any exported function returning results derived from a map either sorts them or documents the order as unspecified, so callers never build on an accident you will later change.
## The rule A Go map is a hash table: keys are hashed, and the hash decides where the entry is stored. Nothing about that storage relates to the order you inserted keys or to their sort order. The Go language specification states plainly that the iteration order over a map is not specified and is not guaranteed to be the same from one iteration to the next. The runtime does not merely leave the order unspecified — it randomises it on purpose. Every time a `for range` loop begins over a map, the runtime chooses a random place in the table to start, and walks from there, wrapping around. Two consecutive loops over a map that nobody touched in between can therefore produce two different sequences. ## Why randomise instead of just leaving it undefined? History. In early Go the order was merely undefined, and for a small map it happened to be stable in practice. People wrote code that relied on it — a printed report, a config dump, a hand-rolled cache eviction — and that code broke later, in production, once the map grew past a threshold and its entries were rehashed into different positions. Randomising converts a rare, delayed, production-only failure into an immediate and obvious one. If your test asserts a particular order, it fails on the first run rather than in six months. This is the same instinct behind the race detector: make the latent bug loud. ## What is actually guaranteed - Every entry that is present in the map for the entire loop is produced exactly once. - An entry deleted before the loop reaches it is not produced. - An entry added during the loop may or may not be produced — that is genuinely unspecified, and you should not write code either way. - The `len` of the map is not fixed for you at loop start; you are ranging over live storage. Mutating a map while ranging over it is legal in the single-goroutine sense (deleting entries during iteration is a common and supported pattern), but adding entries during iteration gives you an unpredictable set. ## Getting a deterministic order There is no ordered map in the standard library. Two patterns cover almost everything: **Sort the keys.** Collect the keys, sort them, then index the map. In recent Go this is one line, because `maps.Keys` yields an iterator over the keys and `slices.Sorted` collects and sorts it: ```go for _, k := range slices.Sorted(maps.Keys(counts)) { fmt.Println(k, counts[k]) } ``` For a key type that is not ordered — a struct, say — collect into a slice and use `slices.SortFunc` with your own comparison, or sort by a derived string. **Keep the order beside the map.** If what you want is insertion order rather than sort order, maintain a slice of keys and append to it when a key is inserted for the first time. The map gives constant-time lookup; the slice gives order. This is what an "ordered map" type would do for you internally. ## Where the undefined order actually bites The symptom is almost never "my loop printed things oddly". It is: - A golden-file test that passes locally and fails in CI, or fails one run in five. - Two runs of a batch job producing byte-different output files for identical input, defeating a downstream diff or a content hash. - A JSON payload whose object key order shifts every response, which is harmless for parsers but noisy in a recorded fixture. - A log line listing "active tenants" that a human is trying to compare across two deployments. The fix in each case is the same and belongs at the boundary: sort at the point where the data leaves the program, not deep inside the aggregation. ## A note on what randomisation is not It is not a security feature in the sense of a per-key secret hash for every workload, and it is not the garbage collector moving things around — Go's collector does not compact. It is simply a randomly chosen starting offset for the walk, cheap to compute and recomputed for each loop. It also costs you nothing to work with, as long as you internalise the one rule: **if the order matters, you produce it yourself.**
- Can a single entry be produced twice in one range over a map?No. Every entry present for the whole loop is produced exactly once. The randomisation only chooses where the walk starts; it does not revisit slots. Entries added during the loop may or may not be produced, and entries deleted before the loop reaches them are not produced at all.
- Why randomise the order rather than simply leaving it unspecified?Because merely unspecified order was stable enough in practice for small maps that people wrote code depending on it, then broke in production once the map grew and entries were rehashed. Randomising makes that dependency fail immediately, in development, instead of silently later.
- What do you build when you need entries in insertion order?Keep a slice of keys alongside the map and append to it on first insert: the map gives constant-time lookup, the slice gives order. Go has no ordered-map type in the standard library, and sorting only helps when the order you want happens to be sort order.
Like dealing from a deck that is cut at a random spot each time: you see every card exactly once, but never in the same sequence.
saying these in an interview costs you the question
- Says map iteration follows insertion order
- Claims the order is stable within a single process run
- Thinks you can sort a map itself rather than its keys
- Assumes two maps with identical keys iterate identically
- Blames the random order on the garbage collector