Skip to content
Theory · Maps · lesson 1 of 9

Maps

What you'll learn

What you'll learn:

  • store key–value pairs in a map and find a value by its key;
  • tell a missing key from a zero value;
  • iterate over a map in a predictable order;
  • explain why writing to a nil map panics while reading from it does not.
An open phone book: in the row Anna the number 12-34-56 stands next to the name

Picture a phone book. You do not look for "the seventh entry from the top" in it, but for a name: you find "Ann" and see her number next to it. The name is the key you search by, and the number is the value you find. You don't have to read the whole book for one number.

Now let's see the same task without such a book. You can store product prices in two slices: one for the names, the other for the prices. To find the price of a pear, you walk the first slice from the start and note the index you find. For data like this, Go has the map, a set of pairs in which a key leads straight to its value:

GO
package main

import "fmt"

func main() {
    prices := map[string]int{
        "pear": 70,
        "plum": 40,
    }
    fmt.Println(prices["pear"]) // 70
    prices["kiwi"] = 90
    fmt.Println(len(prices)) // 3
}

Let's walk through the example line by line:

  1. The literal creates a map with two pairs: a pear costs 70, a plum 40.
  2. prices["pear"] finds the price of a pear, and the program prints 70.
  3. prices["kiwi"] = 90 adds a third pair, so len returns 3.

Type and literal

The type map[string]int reads part by part:

  • map is the keyword: "a set of key–value pairs";
  • [string] means keys of type string;
  • int means values of type int.

Writing a map out in the code like this is also a literal: its braces list the "key: value" pairs. A value is read and written by its key in square brackets, like a slice element: prices["pear"].

Writing by key

The write prices[k] = v follows these rules:

  • writing to a new key adds a pair;
  • writing to an existing key replaces its value;
  • each key appears in a map once;
  • the len function returns the number of pairs.

Here a map is stricter than a phone book: a book can end up with two "Ann" entries, but a map never gets a second such key. A key can be a string, a number or a bool, but not a slice: the type map[[]int]string does not compile.

An empty map

You can create an empty map in two ways. Both give the same result: a map with no pairs that you can write to right away.

The make function

The call make(map[string]int) creates an empty map, the way make creates a slice. The parentheses hold only the map type. A second argument can hint how many pairs you expect: make(map[string]int, 100). It is not a limit: the map grows past it.

Choose make when the map starts empty and is filled later, for example in a loop.

A literal with no pairs

The literal map[string]int{} gives the same result as make(map[string]int). Its braces are empty here, but you can list pairs in them right away, as in the first example. So a literal is handy when some pairs are known in advance.

Fast lookup

A lookup by key does not check the pairs one by one, as a loop over a slice does. The time a read takes hardly depends on how many pairs the map holds: ten or a million. So a map is the choice when values are often looked up by a name, a number or another key.

Go computes a number from the key—a hash—and uses it to decide where to put the pair. Add pairs with the button and watch what happens when the ninth pair runs out of room.

LiveMap: how pairs are storedlen = 8 · groups: 1 · slots: 8 · before growth: 0

The map holds 8 pairs. Up to 8 pairs it is one group of 8 slots, filled in order. Press the button.

Watch the ninth pair

The pairs sit in groups of eight slots. When free slots run low, the map doubles their number and lays every pair out again by its hash.

A missing key

If a name is not in the phone book, the map does not report an error. Reading a missing key returns the zero value of the value type:

  • 0 for int;
  • "" for string.

Deleting a pair

The built-in delete function removes a pair by its key and does nothing for a missing key:

GO
stock := make(map[string]int)
stock["pear"] = 3
fmt.Println(stock["kiwi"]) // 0
delete(stock, "pear")
delete(stock, "lime")
fmt.Println(len(stock)) // 0

Let's walk through the example step by step:

  1. There has never been any kiwi in stock, so the read stock["kiwi"] returns 0.
  2. The first delete removes the pear.
  3. The second delete looks for a lime, finds nothing and does nothing.
  4. No pairs are left in the map, and len prints 0.

A counter with no setup

Because of the zero value, a counter does not need to be set up in advance:

GO
counts := map[string]int{}
counts["go"]++
counts["go"]++
fmt.Println(counts["go"]) // 2

Let's take the two ++ calls in order:

  1. For a new key, counts["go"]++ reads 0, adds one and writes 1.
  2. The next ++ on the same key already reads 1 and writes 2.

Common mistake

The literal has the same key twice:

GO
prices := map[string]int{
    "pear": 70,
    "pear": 75, // ✗
}
fmt.Println(prices)
TEXT
duplicate key "pear" in map literal

The compiler checks the keys of a literal and does not decide for you which price is right. The program does not build until the repeat is fixed. Keep one pair, and to change the price later, assign a new value: prices["pear"] = 75. Such an assignment after the literal is not an error: it replaces the value, as any write to an existing key does.

Key points

  • map[K]V holds key–value pairs; you find a value by its key in square brackets.
  • Writing to a new key adds a pair, writing to an existing one replaces the value; each key appears once.
  • A missing key reads as the zero value, so counts[k]++ works for a new key too.
  • An empty map is created with make(map[string]int) or the literal map[string]int{}.

Subscribe to take the course

The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.

Sign in another way