Kodewick
Start learning →
Lesson 8 of 24Contents

Maps

Lesson 8 of 24 · Part 2 · Data · about 15 minutes

A slice finds elements by index: 0, 1, 2. Often you want to find them by name instead: what apples cost, what Sam scored. That's what a map is for: each key leads to a value.

prog.go
1package main2 3import "fmt"4 5func main() {6	prices := map[string]int{7		"apples": 5,8		"milk":   12,9		"bread":  8,10	}11	fmt.Println(prices["milk"])12	fmt.Println(prices)13}
Output
12
map[apples:5 bread:8 milk:12]

map[string]int is "a map with string keys and int values". Inside the curly braces each entry is key: value, and every line ends with a comma (the last one too). prices["milk"] gets the value for a key.

When fmt.Println shows a whole map, it writes map[key:value …] with the keys in sorted order.

Note: Strings are sorted by their character codes, so "Zoe" comes before "adam": capital letters come first.

Store, change and len

m[key] = value stores an entry: if the key is new it is added, and if it's already there its value is replaced. len(m) is the number of entries. Build an empty map with make:

prog.go
1package main2 3import "fmt"4 5func main() {6	stock := make(map[string]int)7	stock["pencils"] = 408	stock["erasers"] = 159	fmt.Println(stock, len(stock))10 11	stock["pencils"] = 3512	stock["erasers"] += 1013	fmt.Println(stock, len(stock))14}
Output
map[erasers:15 pencils:40] 2
map[erasers:25 pencils:35] 2

A key has only one entry, so after changing "pencils" there are still 2.

Reading a missing key

Reading a key that isn't in the map is not an error: you get the zero value of the value type, 0 for int and "" for string.

Guess first

What does this show?

1package main2 3import "fmt"4 5func main() {6	votes := map[string]int{}7	votes["red"]++8	votes["blue"]++9	votes["red"]++10	fmt.Println(votes["red"], votes["green"], len(votes))11}

But 0 could also be a real stored 0. To tell "stored 0" from "not there at all", read into two variables:

prog.go
1package main2 3import "fmt"4 5func main() {6	stock := map[string]int{"pencils": 40, "rulers": 0}7 8	n, ok := stock["rulers"]9	fmt.Println(n, ok)10	n, ok = stock["glue"]11	fmt.Println(n, ok)12 13	if n, ok := stock["pencils"]; ok {14		fmt.Println("Pencils left:", n)15	}16}
Output
0 true
0 false
Pencils left: 40

ok is true when the key is in the map. The last form is very common: an if with a short statement, using n inside the if.

delete removes an entry

delete(m, key) removes an entry. Deleting a key that isn't there is fine too:

prog.go
1package main2 3import "fmt"4 5func main() {6	cart := map[string]int{"apples": 3, "milk": 1, "bread": 2}7	delete(cart, "milk")8	delete(cart, "eggs")9	fmt.Println(cart, len(cart))10}
Output
map[apples:3 bread:2] 2

The order is not fixed

for k, v := range m goes through every entry. But a map keeps no order: in real Go the order can be different every run (Go shuffles it on purpose, so nobody depends on it). This site uses a fixed order to make stepping easier; don't count on it.

When the result doesn't depend on the order, such as a total, looping directly is fine. To show entries in order, put the keys in a slice, sort it, then read the values in that order:

prog.go
1package main2 3import (4	"fmt"5	"slices"6)7 8func main() {9	scores := map[string]int{"Sam": 88, "Mia": 92, "Dave": 75}10 11	total := 012	for _, v := range scores {13		total += v14	}15	fmt.Println("Total:", total)16 17	var names []string18	for name := range scores {19		names = append(names, name)20	}21	slices.Sort(names)22	for _, name := range names {23		fmt.Println(name, scores[name])24	}25}
Output
Total: 255
Dave 75
Mia 92
Sam 88

for name := range scores takes just the keys. slices.Sort sorts the slice from smallest to largest; it needs import "slices".

Writing to a nil map panics

A map you only declare is nil. Reading it is fine (you get zero values), but writing to it panics:

prog.go
1package main2 3import "fmt"4 5func main() {6	var counts map[string]int7	fmt.Println(counts["a"], len(counts))8	counts["a"] = 19	fmt.Println(counts)10}
Output
0 0
panic: assignment to entry in nil map

goroutine 1 [running]:
main.main(...)
	/tmp/sandbox/prog.go:8 +0x1d
exit status 2

What happened: This map is nil: make it with make(map[…]…) or a literal before you put anything in it.

assignment to entry in nil map: you stored something in a nil map. var counts map[string]int is only a name, with no map behind it yet. Build one with make(map[string]int) or map[string]int{} before you store.

Counting words

Using a map as a counter is one of its most common jobs: the key is a word, and the value is how many times it appeared.

prog.go
1package main2 3import "fmt"4 5func main() {6	words := []string{"apple", "banana", "apple", "orange", "apple", "banana"}7	counts := make(map[string]int)8	for _, w := range words {9		counts[w]++10	}11	fmt.Println(counts)12	fmt.Println("apple appears", counts["apple"], "times")13}
Output
map[apple:3 banana:2 orange:1]
apple appears 3 times

counts[w]++ doesn't need to check whether the key exists: a missing key reads as 0, and adding 1 is just right. Press Step and watch the entries appear and the counts go up. Lesson 9 shows how to split a sentence into words.

Practice

Write the code, then press Check. A hint costs nothing; the answer is there when you need it.

Exercise 1

Check the stock

Finish check: if the item is in stock, return in stock; if not, return no such item. Note that "rulers" is stored as 0, but it is in the map, so it gets in stock (this one only asks whether the key is there).

It should print
in stock
in stock
no such item
exercise1.go
1package main2 3import "fmt"4 5func check(stock map[string]int, item string) string {6	return ""7}8 9func main() {10	stock := map[string]int{"pencils": 40, "rulers": 0}11	fmt.Println(check(stock, "pencils"))12	fmt.Println(check(stock, "rulers"))13	fmt.Println(check(stock, "glue"))14}
Output
Press Run or Ctrl+Enter
Exercise 2

Tally the votes

Finish tally: count how many votes each name got and return a map. main counts two sets of votes and should show the two lines below.

It should print
map[Ann:3 Bo:1 Cy:1]
map[Bo:2]
exercise2.go
1package main2 3import "fmt"4 5func tally(votes []string) map[string]int {6	return nil7}8 9func main() {10	fmt.Println(tally([]string{"Ann", "Bo", "Ann", "Cy", "Ann"}))11	fmt.Println(tally([]string{"Bo", "Bo"}))12}
Output
Press Run or Ctrl+Enter
Exercise 3

Fix it: writing to a nil map

addAll records how many of each item there are, but the program panics as soon as it runs. Fix it so it shows the two lines below.

It should print
map[bread:1 milk:2]
map[eggs:1]
exercise3.go
1package main2 3import "fmt"4 5func addAll(items []string) map[string]int {6	var cart map[string]int7	for _, it := range items {8		cart[it]++9	}10	return cart11}12 13func main() {14	fmt.Println(addAll([]string{"milk", "bread", "milk"}))15	fmt.Println(addAll([]string{"eggs"}))16}
Output
Press Run or Ctrl+Enter

What you learned

  • Build a map with map[string]int{…} or make(map[string]int); m[k] = v stores or changes, m[k] reads, and len(m) counts entries.
  • A missing key reads as the zero value; in v, ok := m[k], ok says whether the key is there.
  • delete(m, k) removes an entry.
  • In real Go, the order of a loop over a map changes from run to run: to show entries in order, put the keys in a slice and slices.Sort it. fmt.Println(m) sorts the keys for you.
  • A map you only declare is nil: reading works, but writing panics with assignment to entry in nil map.
  • counts[w]++ counts things without checking whether the key exists.
Finished this lesson?Mark it. Progress is saved in this browser, and you can undo it.