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:
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) }
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:
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) } }
0 true 0 false pads in stock: 12
Deleting and clearing
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)) }
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:
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 }
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:
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]) } }
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:
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]) } }
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:
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 } }
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:
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)) }
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.
tea 3 coffee 2 juice 1