Maps in Go: Lookups, Iteration Order and the nil Map Trap
Chapter 03 of 04

Maps in Go: Lookups, Iteration Order and the nil Map Trap

Creating and reading maps, the comma-ok idiom, random iteration order, how maps behave when passed to functions, and concurrency safety.

Sabbir Ahmmed3 min read0
On this page

A map stores key-value pairs with fast lookup, insert and delete (O(1) on average). It is Go's built-in hash table, and you will use it for caches, counters, indexes and grouping data.

Creating maps

The type is map[KeyType]ValueType.

go
1
2
3
4
5
6
7
8
9
10
colors := map[string]string{
	"red":   "#ff0000",
	"green": "#00ff00",
	"blue":  "#0000ff",
}

ages := make(map[string]int)    // empty, ready to use
ages["sabbir"] = 28

hint := make(map[int]string, 1000) // size hint: fewer reallocations when you know roughly how many entries

Keys must be comparable with ==: strings, numbers, booleans, pointers, channels, arrays and structs made of comparable fields. Slices, maps and functions cannot be keys.

Reading: the comma-ok idiom

Reading a missing key does not fail. It returns the zero value of the value type. To tell "missing" apart from "present but zero", use the two-value form:

go
1
2
3
4
hex, ok := colors["purple"]
if !ok {
	fmt.Println("purple is not defined")
}

This makes counting very clean, because a missing key starts at 0:

go
1
2
3
4
5
counts := map[string]int{}
for _, word := range []string{"go", "is", "go"} {
	counts[word]++
}
fmt.Println(counts) // map[go:2 is:1]

Updating, deleting, length

go
1
2
3
colors["red"] = "#f00"     // insert or update
delete(colors, "green")    // remove (deleting a missing key is fine)
fmt.Println(len(colors))   // number of entries

Iteration order is random

go
1
2
3
for color, hex := range colors {
	fmt.Printf("%s -> %s\n", color, hex)
}

Go deliberately randomises map iteration order, so code cannot accidentally depend on it. When you need a stable order (for output, tests or APIs), sort the keys first:

go
1
2
3
4
5
6
7
8
import (
	"maps"
	"slices"
)

for _, k := range slices.Sorted(maps.Keys(colors)) { // Go 1.23+
	fmt.Println(k, colors[k])
}

fmt.Println(colors) itself prints keys in sorted order, which is convenient for debugging.

Maps are passed "by reference"... sort of

Everything in Go is passed by value. A map value, however, is a small pointer to the runtime's hash table, so copying the map variable copies the pointer, not the data. A function that receives a map can modify the caller's entries:

go
1
2
3
4
5
6
7
8
9
10
11
12
func paint(c map[string]string) {
	c["red"] = "#ff0000-changed" // visible to the caller
}

func reassign(c map[string]string) {
	c = map[string]string{"new": "map"} // only changes the local variable
}

colors := map[string]string{"red": "#ff0000"}
paint(colors)
reassign(colors)
fmt.Println(colors) // map[red:#ff0000-changed]

Adding or changing entries is visible; pointing the parameter at a brand-new map is not.

The nil map trap

A declared but uninitialised map is nil. Reading from it is allowed, but writing panics:

go
1
2
3
var m map[string]int
fmt.Println(m["x"], len(m)) // 0 0, reads are fine
m["x"] = 1                  // panic: assignment to entry in nil map

Always create maps with a literal or make before writing. In structs, initialise map fields in a constructor function.

Maps vs structs

Use a struct when the fields are known at compile time (a User always has Name and Email). Use a map when keys are dynamic data (counting words, caching by ID). Structs are type-checked and faster; maps are flexible. You can range over a map, but not over a struct's fields (without reflection).

Concurrency warning

Warning
Maps are not safe for concurrent use. If one goroutine writes while another reads or writes, the runtime may crash with fatal error: concurrent map writes. Protect the map with a sync.Mutex / sync.RWMutex, or use sync.Map for the specific case of mostly-read, write-once keys.

Key takeaways

Use make or a literal; writing to a nil map panics.
v, ok := m[k] distinguishes a missing key from a zero value.
Iteration order is random by design; sort keys when order matters.
A function receiving a map can change its entries.
Guard maps shared between goroutines with a mutex.
#Golang#Maps#Data structures

Last updated on

Discussion0