Order matters (and maps don't keep it)
Here's a PHP guarantee you've probably never thought about: an array remembers the order you put things in. foreach walks it in that order, json_encode writes it in that order, and every report you've ever built has quietly relied on it.
Go maps make no such promise. range over a map visits the keys in some order, and Go goes further: it randomises the starting point every time you range. Two loops over the same map, one after the other in the same program, can disagree.
| Run 1 | Oval Bank Angel Moorgate |
|---|---|
| Run 2 | Oval Bank Angel Moorgate |
| Run 3 | Moorgate Oval Bank Angel |
| Your test | passes on your laptop, fails in CI |
That's deliberate. Early Go maps happened to iterate in a fairly stable order, code started depending on it, and it broke whenever the implementation changed. So the Go team made the order unpredictable, on purpose, to stop anyone relying on it.
Sorting the keys
When you want a stable order, usually alphabetical, sort the keys and range over those:
for _, s := range slices.Sorted(maps.Keys(taps)) { fmt.Printf("%s: %d\n", s, taps[s]) }
maps.Keys(taps) hands out the keys one at a time (it's an iterator, which you can range over), and slices.Sorted collects them into a new, sorted slice. You then look each value up as you go. It's PHP's ksort followed by a foreach, except the map itself is never changed.
Sorting by value, such as busiest station first, needs a custom comparison. That's slices.SortFunc, in the next lesson.
Keeping insertion order
Sometimes the order isn't alphabetical at all. A train calls at Bank, then Moorgate, then Angel, and sorting would scramble that. When order matters, keep it in a slice, next to the map:
route := []string{"Bank", "Moorgate", "Angel"} due := map[string]int{ "Bank": 2, "Moorgate": 4, "Angel": 7, } for _, stop := range route { fmt.Println(stop, due[stop]) }
The slice remembers the order, and the map does the fast lookups. Together they're what a PHP array gave you for free. (When each entry has several fields, a slice of structs is often tidier still. That's lesson 2.7.)
Mind the gap
Printing a map looks sorted, and that fools people. fmt.Println(taps) prints map[Angel:1 Bank:3 Oval:2], with the keys in order, every time. That's fmt sorting them to make output readable. It says nothing about range, which is still random. encoding/json sorts map keys too (7.3). So a quick print looks fine, while the loop in your report still shuffles.
Recap
- Ranging over a map visits keys in an unpredictable order, randomised on purpose, even between two loops in one run.
- For alphabetical order:
for _, k := range slices.Sorted(maps.Keys(m)), then look upm[k]. - For insertion order, keep the keys in a slice and use the map for lookups.
fmtandencoding/jsonsort map keys when printing.rangedoesn't.