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.
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: 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:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- Two entries. The literal gives stock two keys, pens with the value 12 and pads with 0. Each key appears once in a map.
- Add and update. stock["clips"] = 40 adds a third key. stock["pens"] -= 2 reads 12, subtracts 2 and stores 10 under the same key.
- 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.
- 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.
- delete. delete(stock, "pads") removes that entry. delete(stock, "pins") finds nothing to remove and does nothing, without an error. Two entries are left.
- 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
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: 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
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: 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
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.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
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.
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:
-
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.
-
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.
-
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.
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;deleteandclearremove entries and never fail. m[k]++counts from zero, andmap[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
- The Go Programming Language Specification: Map types (The Go Authors)
- The Go Programming Language Specification: Index expressions (The Go Authors)
- The Go Programming Language Specification: Deletion of map elements (The Go Authors)
- The Go Programming Language Specification: Clear (The Go Authors)
- The Go Programming Language Specification: For statements with range clause (The Go Authors)
- Go maps in action (The Go Authors)
- Faster Go maps with Swiss Tables (The Go Authors)
- Package maps: Keys and DeleteFunc (The Go Authors)
- The Go Programming Language Specification: Size and alignment guarantees (The Go Authors)
- The Go Programming Language Specification: Assignment statements (The Go Authors)
- The Go Programming Language Specification: Address operators (The Go Authors)
- Data Race Detector (The Go Authors)
- Package slices: Sorted (The Go Authors)
- Go 1.12 Release Notes: fmt prints maps in key-sorted order (The Go Authors)
- Go 1.6 Release Notes: detection of concurrent misuse of maps (The Go Authors)
- Frequently Asked Questions: Why are map operations not defined to be atomic? (The Go Authors)
- Package sync: Map and Mutex (The Go Authors)
Report a problem with this lesson
Kept only in this browser. Your Learn progress