Maps

A map stores values under keys, so you can look a value up directly instead of searching a list: a price by product code, a user by ID, a count by word. Go's built-in map type is a hash table — lookups, inserts and deletes are fast no matter how big it gets. This lesson covers creating maps, the "comma ok" lookup, deleting, iterating in order, maps of slices, and the maps package.

Creating and using a map

The type map[K]V maps keys of type K to values of type V. Create one with a literal or with make:

GO
package main

import "fmt"

func main() {
	prices := map[string]float64{
		"apple":  0.40,
		"banana": 0.25,
	}
	prices["cherry"] = 3.10 // add
	prices["apple"] = 0.45  // update

	fmt.Println(prices["apple"], len(prices))
	fmt.Println(prices)

	stock := make(map[string]int)
	stock["pens"] += 10 // missing keys read as the zero value
	stock["pens"] -= 3
	fmt.Println(stock)
}
Output
0.45 3
map[apple:0.45 banana:0.25 cherry:3.1]
map[pens:7]

Reading a key that isn't in the map returns the value type's zero value — 0, "", false — rather than an error. That makes counting with counts[key]++ work without any setup.

Checking whether a key exists

Because missing keys return zero values, you can't tell "not there" from "there with value 0" by the value alone. The two-value "comma ok" form tells you:

GO
package main

import "fmt"

func main() {
	stock := map[string]int{"pens": 0, "pads": 12}

	qty, ok := stock["pens"]
	fmt.Println(qty, ok)

	qty, ok = stock["glue"]
	fmt.Println(qty, ok)

	if n, found := stock["pads"]; found {
		fmt.Println("pads in stock:", n)
	}
}
Output
0 true
0 false
pads in stock: 12

Deleting and clearing

GO
package main

import "fmt"

func main() {
	sessions := map[string]string{"a1": "ana", "b2": "ben", "c3": "cleo"}
	delete(sessions, "b2")
	delete(sessions, "zz") // deleting a missing key is fine
	fmt.Println(sessions, len(sessions))

	clear(sessions) // remove everything
	fmt.Println(sessions, len(sessions))
}
Output
map[a1:ana c3:cleo] 2
map[] 0

The nil map

The zero value of a map type is nil. A nil map behaves like an empty map for reading, but writing to it panics. Always create maps with make or a literal before storing into them:

GO
package main

import "fmt"

func main() {
	var config map[string]string
	fmt.Println(config["mode"], len(config)) // reading is fine
	config["mode"] = "fast"                  // writing panics
}
Output
 0
panic: assignment to entry in nil map

goroutine 1 [running]:
…
exit status 2

Iterating

A for … range over a map gives keys and values in an unspecified order that differs between runs. To process entries in a fixed order, sort the keys:

GO
package main

import (
	"fmt"
	"maps"
	"slices"
)

func main() {
	population := map[string]int{"Lisbon": 545_000, "Porto": 232_000, "Braga": 193_000}

	for _, city := range slices.Sorted(maps.Keys(population)) {
		fmt.Printf("%-7s %7d\n", city, population[city])
	}
}
Output
Braga    193000
Lisbon   545000
Porto    232000

Deleting entries during a range loop is allowed. Adding entries during the loop is allowed too, but the new entries may or may not be visited.

Counting and grouping

Two patterns you'll write again and again: counting occurrences, and grouping items into a map of slices:

GO
package main

import (
	"fmt"
	"maps"
	"slices"
	"strings"
)

func main() {
	text := "the cat and the hat and the bat"
	counts := map[string]int{}
	for _, w := range strings.Fields(text) {
		counts[w]++
	}
	fmt.Println(counts)

	byFirst := map[byte][]string{}
	for _, w := range []string{"apple", "avocado", "banana", "blueberry", "cherry"} {
		byFirst[w[0]] = append(byFirst[w[0]], w) // appending to a missing (nil) slice works
	}
	for _, k := range slices.Sorted(maps.Keys(byFirst)) {
		fmt.Printf("%c: %v\n", k, byFirst[k])
	}
}
Output
map[and:2 bat:1 cat:1 hat:1 the:3]
a: [apple avocado]
b: [banana blueberry]
c: [cherry]

Key types

Keys can be any comparable type: numbers, strings, booleans, pointers, channels, arrays, and structs whose fields are all comparable. Slices, maps and functions can't be keys. A struct key is a neat way to index by several values at once:

GO
package main

import "fmt"

type cell struct{ row, col int }

func main() {
	board := map[cell]string{}
	board[cell{0, 0}] = "X"
	board[cell{1, 2}] = "O"
	fmt.Println(board[cell{1, 2}], len(board))

	seen := map[string]bool{} // a set: only the keys matter
	for _, id := range []string{"a", "b", "a"} {
		if seen[id] {
			fmt.Println("duplicate:", id)
		}
		seen[id] = true
	}
}
Output
O 2
duplicate: a

Go has no separate set type; a map[T]bool (or map[T]struct{}, which uses no memory for values) does the job.

Maps are references

A map value is a small handle to the underlying table. Assigning a map or passing it to a function doesn't copy the entries — both names refer to the same map. Use maps.Clone for an independent copy:

GO
package main

import (
	"fmt"
	"maps"
)

func addBonus(scores map[string]int) {
	for name := range scores {
		scores[name] += 5
	}
}

func main() {
	scores := map[string]int{"ana": 80, "ben": 72}
	backup := maps.Clone(scores)
	addBonus(scores)
	fmt.Println(scores, backup)
	fmt.Println(maps.Equal(scores, backup))
}
Output
map[ana:85 ben:77] map[ana:80 ben:72]
false

Maps are not safe for concurrent use: if several goroutines write to a map at once, the program crashes. Lesson 54 shows how to protect a map with a mutex.

Try it yourself

Count the votes and print the results from most to fewest votes.

Try it yourself · main.go
Edit the code, copy it into main.go in a new folder and run it with go run main.go (Go 1.27 or later). How to install Go
Output
tea 3
coffee 2
juice 1
Next: sorting with custom orders.