Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Go  Module 4 – Arrays, slices, maps and strings

Maps: lookups, sets, iteration and gotchas

Read, write and delete Go map entries, tell a missing key from a zero value with comma-ok, count and dedupe with maps, and print them in a stable order.

  • Beginner
  • 15 minutes
  • Examples run with Go 1.27.1 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 6 of 15 minutes free
Free: A lookup returns a value, and ok says whether the key was there · Maps count, and map[T]struct makes a set · The order of a range loop changes; sort the keys for stable output. With a pass: 2 in-depth parts and 3 interview answers, about 9 minutes.

What you will learn

  • Create, read, update and delete map entries, and tell a missing key from a zero value with comma-ok
  • Count with maps and build sets that keep the order in which values first appear
  • Sort the keys of a map to print it in a stable order, although its iteration order varies
  • Prevent the nil-map panic, the unassignable map element and crashes from concurrent writes
  • Explain how Go stores map entries, size maps with a hint and release the memory of a shrunken map

Before you start

What you’ll learn · Before you start

What you will learn

  • Create, read, update and delete map entries, and tell a missing key from a zero value with comma-ok
  • Count with maps and build sets that keep the order in which values first appear
  • Sort the keys of a map to print it in a stable order, although its iteration order varies
  • Prevent the nil-map panic, the unassignable map element and crashes from concurrent writes
  • Explain how Go stores map entries, size maps with a hint and release the memory of a shrunken map

Before you start

On this page

stock["pads"] returns 0 for pads that are out of stock and also 0 for pins, a product the shop has never stocked: a Go map: A built-in type that stores values under unique keys of one comparable type, written map[K]V. A lookup of a missing key gives the zero value of V, and the order of iteration is not specified. answers every lookup, and a missing key gets the zero value: The value a variable holds when it is given none: 0 for numbers, false for booleans, the empty string for strings, nil for pointers, slices, maps, channels, functions and interfaces, and zeroed fields or elements for structs and arrays. More in “Variables, short declarations and zero values”. The two-value form, n, ok := stock["pins"], is what tells “none left” from “no such key”.

You will read, write and delete entries, count and remove duplicates with maps, and print a map in an order that does not change between runs. Every output on this page is the one the program printed with Go 1.27.1.

A lookup returns a value, and ok says whether the key was there

A map stores values under keys of one comparable type, and finds a value by its key. This program goes through every basic operation on a small stock list:

stock.go: add, update, look up, delete and clear Go · stock.go
// stock.go: reading, writing and deleting map entries, and telling a missing key from a zero value.
package main

import "fmt"

func main() {
	stock := map[string]int{"pens": 12, "pads": 0} // a map literal
	stock["clips"] = 40                            // adds a key
	stock["pens"] -= 2                             // updates one

	fmt.Println(stock["pens"], stock["pads"], stock["pins"]) // pins was never added
	n, ok := stock["pads"]
	fmt.Println("pads:", n, ok)
	n, ok = stock["pins"]
	fmt.Println("pins:", n, ok)

	delete(stock, "pads")
	delete(stock, "pins") // deleting a key that is not there does nothing
	fmt.Println(len(stock), stock)

	clear(stock) // removes every entry; the map itself stays usable
	fmt.Println(len(stock), stock == nil)
}

Output

10 0 0
pads: 0 true
pins: 0 false
2 map[clips:40 pens:10]
0 false

Recorded with Go 1.27.1 on macOS 26 arm64. To run it yourself: go run stock.go

Step through it and watch the entries change:

Interactive · step by step What each line of stock.go does to the map Step through stock.go and watch the entries of stock, and what a lookup of a missing key returns.
What each line of stock.go does to the map, step 6 of 6. Showing stock (empty, not nil). In focus: stock.stockempty, not nil

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Two entries. The literal gives stock two keys, pens with the value 12 and pads with 0. Each key appears once in a map.
  2. Add and update. stock["clips"] = 40 adds a third key. stock["pens"] -= 2 reads 12, subtracts 2 and stores 10 under the same key.
  3. Two zeros. stock["pads"] finds its key and returns its value, 0. stock["pins"] finds no key and returns the zero value of int, also 0. The program printed 10 0 0.
  4. ok tells them apart. The two-value form adds a bool. n, ok := stock["pads"] gave 0 true, and the same lookup of pins gave 0 false.
  5. delete. delete(stock, "pads") removes that entry. delete(stock, "pins") finds nothing to remove and does nothing, without an error. Two entries are left.
  6. clear. clear(stock) deletes every entry. The map is empty, len 0, but it is still a map, not nil, so it can take new entries at once.

The first lookups printed 10 0 0: pads and pins looked alike. The specification says an index expression on a map gives the element for the key if the map holds one, and the zero value of the element type if it does not. The comma-ok: The two-value form of an operation that may find nothing, such as v, ok := m[k]. ok reports whether the key was present, so a missing key can be told from a stored zero value. form adds a bool, ok, which is true only when the key is present: 0 true for pads, 0 false for pins. delete removes an entry and does nothing for a missing key, and clear, a built-in since Go 1.21, removes every entry and leaves an empty map, not a nil one.

Testing a value to learn whether a key exists

if stock[name] == 0 cannot tell an empty shelf from an unknown product. Use if _, ok := stock[name]; !ok when the question is whether the key is there.

Checkpoint

Check: Lookups, comma-ok and delete

  1. Question 1 of 2 What does this print?

    Read the code, then choose one answer.

    ages := map[string]int{"asha": 31}
    fmt.Println(ages["ravi"], len(ages))
    Show the answer: Question 1 of 2

    Answer: 0 1

    stock["pins"] printed 0 in stock.go in the same way: the zero value for a key that is not there.

  2. Question 2 of 2 Which check prints "unknown product" only for a key that is not in stock?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: if _, ok := stock[name]; !ok { fmt.Println("unknown product") }

    The comma-ok form told pads, 0 true, from pins, 0 false, in stock.go.

Maps count, and map[T]struct makes a set

Because a missing key reads as the zero value, counts[w]++ works on the first sighting of a word: it reads 0 and stores 1. And a map whose values carry nothing is a set: A collection of distinct values. Go has no set type; a map whose values carry nothing, map[T]struct{}, serves as one, its keys being the members. of its keys:

words.go: word counts, and words in order of first appearance Go · words.go
// words.go: counting words with m[w]++, and a set that keeps the order in which words first appear.
package main

import (
	"fmt"
	"strings"
)

func main() {
	text := "the cat and the hat and the bat"
	counts := map[string]int{}
	for _, w := range strings.Fields(text) {
		counts[w]++ // a missing key reads as 0, so its first ++ stores 1
	}
	fmt.Println(counts["the"], counts["and"], counts["dog"], len(counts))

	seen := map[string]struct{}{} // a set: only the keys matter
	var firstSeen []string
	for _, w := range strings.Fields(text) {
		if _, ok := seen[w]; ok {
			continue
		}
		seen[w] = struct{}{}
		firstSeen = append(firstSeen, w)
	}
	fmt.Println(firstSeen, len(seen))
}

Output

3 2 0 5
[the cat and hat bat] 5

Recorded with Go 1.27.1 on macOS 26 arm64. To run it yourself: go run words.go

“the” was counted 3 times and “dog”, never seen, read as 0. The set used struct{} as its value type, a struct with no fields, which the specification gives size zero, so the map stores only keys. The slice firstSeen kept the order that the set alone cannot: each word was appended the first time the set did not know it, giving [the cat and hat bat]. The pattern, a set to ask and a slice to keep, removes duplicates without changing order, which sorting cannot do.

Checkpoint

Check: Counting and sets

  1. Question 1 of 2 What does this print?

    Read the code, then choose one answer.

    c := map[rune]int{}
    for _, r := range "banana" {
    	c[r]++
    }
    fmt.Println(c['a'], c['n'], c['x'])
    Show the answer: Question 1 of 2

    Answer: 3 2 0

    counts[w]++ in words.go counted "the" 3 times without any check for a first sighting.

  2. Question 2 of 2 Why is map[string]struct{} the usual type for a set of strings?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Only the keys matter, and struct{} values take no space

    A set asks only whether a key is present, which the comma-ok form answers; the value has nothing to say.

The order of a range loop changes; sort the keys for stable output

The specification leaves the order of a range loop over a map open, and does not promise that two loops over the same map agree. This program ranges over one map 100 times and counts the orders it saw:

order.go: 100 loops over six keys, then the keys sorted Go · order.go
// order.go: a hundred loops over one map, then its keys sorted for output that never changes.
package main

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

func main() {
	visits := map[string]int{"Accra": 1, "Kyoto": 2, "Lima": 3, "Oslo": 4, "Pune": 5, "Quito": 6}

	orders := map[string]bool{} // each order a loop visited the keys in, as one string
	first := ""
	for range 100 {
		var keys []string
		for city := range visits {
			keys = append(keys, city)
		}
		order := strings.Join(keys, " ")
		if first == "" {
			first = order
		}
		orders[order] = true
	}
	fmt.Println("first loop:", first)
	fmt.Println("orders seen in 100 loops:", len(orders))

	fmt.Println("sorted:", strings.Join(slices.Sorted(maps.Keys(visits)), " "))
	fmt.Println("fmt:", visits) // fmt prints a map's entries sorted by key
}

Output

first loop: Kyoto Lima Oslo Pune Quito Accra
orders seen in 100 loops: 6
sorted: Accra Kyoto Lima Oslo Pune Quito
fmt: map[Accra:1 Kyoto:2 Lima:3 Oslo:4 Pune:5 Quito:6]

This output changes from run to run: a range loop over a map may start at a different key each time it runs, so the first order and the count change between runs

Recorded with Go 1.27.1 on macOS 26 arm64. To run it yourself: go run order.go

The loops did not agree, and the first one can start at a different key each time the program runs, so code that prints a map in a loop prints it differently from run to run. For stable output, collect the keys and sort them: slices.Sorted(maps.Keys(visits)) does both in one line, since Go 1.23. fmt needs no help: since Go 1.12 it prints maps sorted by key, which is why the last line is the same on every run.

Key idea

A map answers lookups, not questions of order: when output must be stable, sort the keys first.

Checkpoint

Check: Iteration order and stable output

  1. Question 1 of 2 A function builds "a=1,b=2" by ranging over a map, and its test fails now and then with nothing changed. What is the fix?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Range over slices.Sorted(maps.Keys(m)) and read each value by its key

    order.go saw several orders in 100 loops over one map; sorting the keys is what makes output stable.

  2. Question 2 of 2 What does fmt.Println(map[string]int{"b": 2, "a": 1}) print?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: map[a:1 b:2], on every run

    The last line of order.go was the same on every run because fmt sorts map keys.

Three failures are worth knowing by name. Writing to a nil map panics. A field of a struct stored in a map cannot be assigned in place. Two goroutines writing to one map crash the whole program. Inside, a map is a Swiss table that keeps entries in groups of eight slots, a size hint saves regrowth, and deleting entries does not give memory back.

Nil maps, unassignable elements and concurrent writes, reproduced Pro: opens with a Pro, Premium or Ultimate pass

Three map mistakes compile or run without warning until they stop a program. This part reproduces each one on Go 1.27.1, the panic, the compiler’s refusal and the crash, next to the code that avoids it.

In this part:

  • Writing to a nil map panics, and reading it does not
  • A field of a map element cannot be assigned
  • Two goroutines writing one map stop the whole program

6 runnable examples (Go, Python) · about 5 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Inside a map: Swiss tables, size hints and memory, measured Pro: opens with a Pro, Premium or Ultimate pass

Since Go 1.24 the built-in map is a Swiss table, and three practical rules follow from how it works: give a size hint when you know the size, expect a map not to shrink, and know what a loop may change. This part shows the lookup step by step and measures the other two.

In this part:

  • A lookup checks eight slots at once
  • A size hint saves the regrowth, counted
  • Deleting entries does not give memory back
  • Deleting during a loop is safe; adding is unpredictable

1 diagram · 3 runnable examples (Go) · about 6 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Practise

Practice works through lookups, counting and sets, and stable output, beginning where your earlier answers were least sure. Every option carries its own explanation:

Practice that adapts to you

Practise this lesson

9 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 9 What does m[k] return when k is not a key of m?

    Choose one answer.

    Show the answer: Question 1 of 9

    Answer: The zero value of the map's value type

    A missing key reads as 0 for ints, "" for strings and nil for pointers: the value type's zero value.

  2. Question 2 of 9 What does this print?

    Read the code, then choose one answer.

    m := map[string]int{"a": 1}
    delete(m, "b")
    delete(m, "a")
    fmt.Println(len(m), m == nil)
    Show the answer: Question 2 of 9

    Answer: 0 false

    delete and clear remove entries and never fail; the map stays usable, as clear(stock) left it in stock.go.

  3. Question 3 of 9 A function returns price, ok := prices[item] and callers check only price > 0. Free samples have price 0. What goes wrong?

    Choose one answer.

    Show the answer: Question 3 of 9

    Answer: Free samples and unknown items both look missing, because both give 0; callers must check ok

    Whenever the zero value is also a valid value, the comma-ok form is the only way to know a key is present.

  4. Question 4 of 9 With a set of seen words and a slice of first sightings, as in words.go, what comes out of the input b a b c a?

    Read the code, then choose one answer.

    seen := map[string]struct{}{}
    var out []string
    for _, w := range []string{"b", "a", "b", "c", "a"} {
    	if _, ok := seen[w]; ok {
    		continue
    	}
    	seen[w] = struct{}{}
    	out = append(out, w)
    }
    fmt.Println(out)
    Show the answer: Question 4 of 9

    Answer: [b a c]

    A set answers "seen before?" and the slice remembers the order, which a map alone cannot.

  5. Question 5 of 9 counts[w]++ runs for every word of "to be or not to be". What is len(counts) afterwards?

    Type a number.

    Show the answer: Question 5 of 9

    Answer: 4 keys

    The six words hold four different ones, to, be, or and not, so the map has 4 keys; counts["to"] is 2.

  6. Question 6 of 9 You must remove repeated email addresses from a list and keep the rest in the order the user entered them. Which approach?

    Choose one answer.

    Show the answer: Question 6 of 9

    Answer: A set of seen addresses and a new slice of first sightings

    words.go did exactly this with words: map[string]struct{} to ask, a slice to keep the order.

  7. Question 7 of 9 What does the Go specification promise about the order of a range loop over a map?

    Choose one answer.

    Show the answer: Question 7 of 9

    Answer: Nothing; the order is not specified and may differ from one loop to the next

    Code must never depend on map iteration order; sort the keys when order matters.

  8. Question 8 of 9 Which loop visits the keys of m in the same order on every run?

    Choose one answer.

    Show the answer: Question 8 of 9

    Answer: for _, k := range slices.Sorted(maps.Keys(m)) { … }

    slices.Sorted collects an iterator's values into a sorted slice, giving stable output.

  9. Question 9 of 9 Two copies of a service render the same map as an HTML list by ranging over it, and a cache that compares pages keeps finding them different. Why, and what fixes it?

    Choose one answer.

    Show the answer: Question 9 of 9

    Answer: Each loop may visit the keys in another order; render from the sorted keys

    Whenever output is compared, cached or hashed, map order must be removed by sorting.

Interview questions

Warm-up (fresher): How do you tell a missing key from a key that holds the zero value?

Use the two-value form of the lookup, v, ok := m[key]. ok is true when the key is in the map, whatever its value, and false when it is not, in which case v is the zero value. A plain m[key] returns the zero value in both cases, so it cannot tell them apart.

Three longer questions of the module bank build on this lesson:

  1. Mid to senior

    An HTTP service keeps sessions in a package-level map[string]Session that every handler reads and writes. Under load it dies with fatal error: concurrent map writes, and the recover in its middleware never runs. Explain why, then fix it and say how you would catch it in tests.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A cache map held 10 million entries at a traffic peak. A nightly purge deletes all but 50,000 of them, yet the process's heap stays near its peak for days. Explain what you see, and fix it without restarting the process.

    Model answer, Pro. First step and follow-ups

  3. Fresher to mid

    byUser is a map[string]Stats, and byUser[id].Visits++ does not compile. Explain the error, give two ways to make the change, and say when each fits.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A lookup of a missing key returns the zero value, and the comma-ok form, v, ok := m[k], says whether the key was there; delete and clear remove entries and never fail.
  • m[k]++ counts from zero, and map[T]struct{} is a set, which with a slice removes duplicates and keeps the order of first appearance.
  • Range over a map visits keys in an order that changes between loops and runs; sort the keys, with slices.Sorted(maps.Keys(m)), for stable output.

Terms in this lesson

comma-ok
The two-value form of an operation that may find nothing, such as v, ok := m[k]. ok reports whether the key was present, so a missing key can be told from a stored zero value.
map
A built-in type that stores values under unique keys of one comparable type, written map[K]V. A lookup of a missing key gives the zero value of V, and the order of iteration is not specified.
set
A collection of distinct values. Go has no set type; a map whose values carry nothing, map[T]struct{}, serves as one, its keys being the members.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.