skip to content

Why does fmt.Println print a Go map's keys in sorted order when a range loop over the same map does not?

level: middleimportance: nice to knowfreq 32%

answer

  1. the sorting is not in the map
  2. someone sorts on your behalf
  3. printing and marshalling both do it
  4. added so tests and diffs stop flaking
  5. your own range loop gets no such favour

basics

~20 s

The fmt package sorts a map's keys itself before printing, so its output is stable. The map is still unordered; ranging over it directly gives a randomised order. encoding/json sorts map keys when marshalling for the same reason.

solid answer

~40 s

Sorting happens in the printer, not in the map. Since Go 1.12 the `fmt` package sorts a map's keys before formatting it, precisely so that printed output and golden tests are reproducible; `encoding/json` likewise emits map keys in sorted order when marshalling. Neither changes the map: a `for k := range m` loop still starts at a random position and yields a random order. This matters because it is a trap during debugging — you print the map, see stable sorted output, and conclude the map is ordered, then the code that actually walks the map produces a different result every run. The rule to carry away is that stability you observed came from a package that sorted on your behalf, and it does not transfer to your own iteration.

code

go · 6 lines
go
m := map[string]int{"b": 2, "a": 1}
fmt.Println(m)          // prints: map[a:1 b:2]
b, _ := json.Marshal(m) // b is: {"a":1,"b":2}
for k := range m {
	fmt.Print(k) // order is not specified
}

go deeper

for a junior

Remember the one-line fact: printing a map shows sorted keys because the printing package sorts them, and that tells you nothing about how a loop over the map will behave.

for a middle

Be able to name both places the sorting happens — the fmt package when formatting and encoding/json when marshalling — and to state that the map itself is unchanged by either.

for a senior

Use it as a debugging discipline: when someone offers printed map output as evidence that ordering is not the problem, point out that the print path is the one path that sorts, and ask for the artefact the real code produced.

for a principal

Decide as a team rule where determinism is asserted. Relying on another package's formatting for stable output makes that package's implementation part of your contract; an explicit sort at the emission site keeps the guarantee where a reader can see it.

## Two different things are being confused There is the map's *iteration order*, which is unspecified and randomised, and there is the *rendering* of a map by a package that formats it. They are independent, and the second one is deterministic. ### fmt sorts before it prints When you hand a map to `fmt.Println`, `fmt.Printf("%v")` or `fmt.Sprint`, the `fmt` package extracts the keys, sorts them with an internal ordering, and then formats the entries in that order. This was introduced in Go 1.12 with an explicitly stated motivation: making printed output reproducible so that tests and diffs of debug output stop flaking. The internal ordering is defined for every comparable key type, not just strings — numeric keys compare by value, strings by their bytes, booleans false before true, pointers and channels by address, and structs and arrays field by field or element by element. So `fmt` gives you a stable rendering even for exotic key types, though for a pointer-keyed map the order is stable within a run rather than meaningful. ### encoding/json sorts too Marshalling a Go map to JSON also emits the keys in sorted order. That is why a JSON body produced from a map does not churn between runs, and it is one reason marshalling to JSON is a reasonable way to snapshot map contents in a test. ### The map itself is untouched Neither package reorders the map, caches an order in it, or gives you a handle on that order. Write the loop yourself: ```go for k := range m { fmt.Print(k) // order is not specified } ``` and you are back to a randomised start. `fmt.Println(m)` and the loop above can disagree on the very next line of the same program. ## Why this specific confusion is expensive The usual failure story runs like this. A developer building output from a map notices the result is inconsistent, adds a `fmt.Println(m)` to see what is in it, and the printed map looks perfectly ordered. The conclusion drawn — "the map is fine, the bug is elsewhere" — sends the investigation in the wrong direction, because the one piece of evidence collected was produced by the only code path in the program that sorts. The same trap appears in reverse in code review: a reviewer sees a test asserting on `fmt.Sprintf("%v", m)` and flags it as order-dependent, when in fact that particular assertion is stable. Both directions are worth being precise about. ## What this does and does not license - **It does license** using `%v` on a map, or marshalling it to JSON, as a deterministic snapshot in a test — with the caveat that you are then asserting on a format that belongs to another package, so a formatting change is a test change. - **It does not license** assuming any other iteration is ordered. Anything you build by ranging — a concatenated string, a slice of rows, a hash, a generated file — needs its own sort. - **It does not make printing a substitute for a defined output contract.** If the order of a rendered artefact matters to a consumer, sort explicitly at the point of emission so the intent is visible in the code rather than inherited from a printer's implementation detail. ## What an interviewer is listening for A candidate who knows the fact at all is unusual, so this is a differentiator rather than a screening question. The strong answer separates the printer from the container in one sentence, names both `fmt` and `encoding/json` as places where the sorting happens, and immediately draws the practical consequence: stable printed output is not evidence that your own loop over the map is stable.

  • Does encoding/json also sort map keys when it marshals?
    Yes. Marshalling a Go map emits its keys in sorted order, which is why JSON produced from a map is byte-stable between runs. It is the same motivation as in fmt: reproducible output. It does not change the map, so building the JSON yourself by ranging would not be stable.
  • Does fmt's sorting work for a map whose keys are not strings?
    Yes. The fmt package defines an ordering over every comparable key type — numbers by value, strings by bytes, booleans with false first, pointers and channels by address, structs and arrays element by element — so the rendering is stable regardless of the key type, even where the resulting order is arbitrary in meaning.
  • Is asserting on fmt.Sprintf("%v", m) an acceptable way to test map contents?
    It is deterministic, so it will not flake, but it couples the test to another package's formatting and reads poorly on failure. Comparing maps directly with an equality helper, or sorting the keys and asserting on structured values, expresses the intent better and gives a clearer diff.

saying these in an interview costs you the question

  • Concludes the map is ordered because printing it looked sorted
  • Thinks fmt reorders or caches an order inside the map
  • Assumes only string-keyed maps print in a stable order
  • Believes encoding/json emits map keys in range order
  • Uses a printed map as proof that a range loop is stable