Insertion Order Iteration of Maps in Go

Recently, I discovered that map contents in Go are randomized. This was odd to me coming from Ruby. In Ruby, iterating through a map (called “hash” in Ruby) is always in insertion order:

# example.rb

# define the hash
h = { a: 0,
      b: 1,
      c: 2,
      d: 3,
      e: 4,
      f: 5 }

# iterate through hash and print each key, value
h.each do |key, value|
  puts "Key: #{key}, Value: #{value}"
end
$ ruby example.rb

Key: a, Value: 0
Key: b, Value: 1
Key: c, Value: 2
Key: d, Value: 3
Key: e, Value: 4
Key: f, Value: 5

But Go doesn’t care about insertion order. Elements in a map are always random when iterating through them:

// example.go

package main

import "fmt"

func main() {
	m := make(map[string]int)

	m["a"] = 0
	m["b"] = 1
	m["c"] = 2
	m["d"] = 3
	m["e"] = 4
	m["f"] = 5

	// iterate through map and print each key, value
	for key, value := range m {
		fmt.Println("Key:", key, "\t", "Value:", value)
	}
}
$ go run example.go

Key: b 	 Value: 1
Key: c 	 Value: 2
Key: d 	 Value: 3
Key: e 	 Value: 4
Key: f 	 Value: 5
Key: a 	 Value: 0

What threw me off even more is that printing the map without a loop prints elements in insertion order:

// example.go

package main

import "fmt"

func main() {
	m := make(map[string]int)

	m["a"] = 0
	m["b"] = 1
	m["c"] = 2
	m["d"] = 3
	m["e"] = 4
	m["f"] = 5

	// print map directly
	fmt.Println(m)
}
$ go run example.go

map[a:0 b:1 c:2 d:3 e:4 f:5]

The Solution

To iterate through map contents in insert order, we need to create a slice that keeps track of each key. Then, instead of iterating through the map, we iterate through the slice and use its contents (which are the map keys) to access the map’s values in the order in which they were inserted:

package main

import "fmt"

func main() {
	m := make(map[string]int)
	m["a"] = 0
	m["b"] = 1
	m["c"] = 2
	m["d"] = 3
	m["e"] = 4
	m["f"] = 5

	// store map keys in a slice
	keys := []string{"a", "b", "c", "d", "e", "f"}

	// iterate through "keys" slice to get map values in insert order
	// the underscore is there because we wont be using the first value, which is the index of the slice
	for _, key := range keys {
		fmt.Println("Key:", key, "\t", "Value:", m[key])
	}
}
$ go run example.go

Key: a 	 Value: 0
Key: b 	 Value: 1
Key: c 	 Value: 2
Key: d 	 Value: 3
Key: e 	 Value: 4
Key: f 	 Value: 5

Now each key and value is printed in insertion order.