Go: from zero to your own blog Lesson 9 of 50
Maps in Go: key and value
The ninth lesson of the Go course. A map is a pair of key and value that finds an entry at once instead of walking a list. How to write and read one, why a missing key hands back zero rather than an error, what comma-ok is, why Go shuffles the iteration order on purpose, and how a nil map is more dangerous than a nil slice.
Why this matters
In the previous lesson the list of articles grew exactly as it should. But finding an article in it by address means walking the whole thing: a hundred articles, a hundred comparisons; a thousand, a thousand. And the browser has to be answered at once.
For that Go has a second way to hold many values — a map. The word is the same in the code, in the documentation and in the error messages, so there is only one term to learn here.
Picture it. A dictionary. You do not read it from the first page until you reach the word you want — you open it straight at the right letter. A list searches by walking; a dictionary answers at once.
The whole thing at once
A new folder, go mod init sabaq08, main.go:
package main
import (
"fmt"
"slices"
)
func main() {
views := map[string]int{
"go": 120,
"kazakh": 45,
"web": 30,
}
views["go"]++
views["steppe"] = 7
fmt.Println("tags:", len(views))
fmt.Println("go:", views["go"])
fmt.Println("music:", views["music"])
n, ok := views["music"]
fmt.Println("value:", n, "· key present:", ok)
keys := make([]string, 0, len(views))
for k := range views {
keys = append(keys, k)
}
slices.Sort(keys)
for _, k := range keys {
fmt.Println(k, views[k])
}
}
Before you run it, and without looking below, say out loud what it will print. Then run it and compare: the difference is exactly where you do not know the language yet.
go run . prints:
tags: 4
go: 121
music: 0
value: 0 · key present: false
go 121
kazakh 45
steppe 7
web 30
We never added a tag called “музыка” — and still got an answer.
Taking it apart
map[string]int — from what to what
The declaration reads left to right: inside the square brackets the type of the key, immediately after them the type of the value. map[string]int goes from a string to a number: a view count per tag.
Not everything can be a key, only what can be compared for equality: a string, a number, a bool. A slice cannot — precisely because Go refuses to compare two slices.
Writing and reading
views["steppe"] = 7 // written
n := views["go"] // read
views["go"]++ // read, incremented, written back
The brackets are the ones you know from slices, but inside sits a key rather than a position. No append is needed: assigning to a new key is what adding means here.
The literal at the top of the program is the same thing written shorter: pairs separated by a colon, and a comma at the end of every line, the last one included.
A missing key is not an error, it is zero
views["music"] printed 0 even though no such tag exists. Go neither stopped the program nor complained: reading a key that is not there gives the zero value of the type in the declaration. For int that is 0, for string the empty string, for bool it is false.
Picture it. An empty shelf in a warehouse. You asked for the “музыка” shelf and were not told there is no such thing — you were shown an empty shelf. There is nothing on it, and there is no error either.
The counter in the exercise rests on this: m[tag]++ works for a tag that was never there, because it simply starts from zero.
Comma-ok: asking whether the key exists
Sometimes the difference matters: zero views and “no such tag” are two different things. Then you ask the map with two variables:
n, ok := views["music"] // 0, false
The second value is true when the key is there and false when it is not. This form is called comma-ok, and you will meet it more than once.
The iteration order is not promised
range over a map walks every pair, but the order may come out different each time. This is not sloppiness but a decision: Go deliberately starts the walk at a different place so that nobody writes code that depends on the order.
See for yourself: walk the map without sorting and run the program ten times in a row — you will see several different orders.
Picture it. A deck shuffled before every deal. The cards are the same ones — but anyone who counted on their order is going to be wrong.
When you do need an order, collect the keys into a slice and sort them, as in the program above. slices.Sort lives in the standard library’s slices package — a name you know from the previous lesson. And note make([]string, 0, len(views)): length zero, capacity known in advance, not a single move.
delete and len
len(views) is how many pairs the map holds. delete(views, "web") removes a pair; if the key is not there nothing happens, and that is not an error either.
A nil map: reading is fine, writing is not
Here a map behaves unlike a slice, and the difference is worth remembering straight away.
var m map[string]int
fmt.Println(m["go"]) // 0 — reading is fine
m["go"] = 1 // panic: assignment to entry in nil map
In the previous lesson var titles []string took an append without complaint. A var m map[string]int will not take a write: the program stops. A map has to be created — make(map[string]int), or the literal map[string]int{}.
The lesson map
Say it in your own words
Without looking, answer out loud or on paper. The answers are at the end of the lesson.
- Why did
views["music"]return zero instead of stopping the program? How do you tell “zero views” apart from “no such tag”? - Why does Go deliberately vary the order in which a map is walked?
var titles []stringworks withappend, whilevar m map[string]intfalls over on the very first write. What is the difference?
Exercise
Required. Write a function count(tags []string) map[string]int that counts how many times each tag appears. Call it on []string{"go", "web", "go", "steppe", "go", "web"} and print the result in alphabetical order of the keys — the map itself will not give you that order.
Optional.
has(m map[string]int, k string) bool, written with comma-ok.- Delete a key with
deleteand printlenbefore and after. - Try writing into a
var m map[string]intand read the whole crash message. Being able to read apanicis half of debugging.
Where this goes in your blog
Routing is a map: find the handler for an address without walking them all. A view counter per tag is a map. The three languages of one article are a map too: the text for each sits under the key kz, ru or en — that is exactly how the site you are reading is built. In the lesson on structs the value will stop being a number and become a whole article.
Build your own blog. This is where the last two lessons meet: the slice holds the order of the articles on the front page, and the maps find a title and a length by address. Neither could stand in for the other, and step-3 shows that line by line.
Answers
Show the answers
- Because reading a missing key in Go gives the zero value, which for
intis zero. To tell the two apart, use the two-variable form:n, ok := views["music"], whereokisfalsewhen the key is absent. - So that nobody comes to rely on an order the map never promised. If the order happened to be stable, programs would grow on top of it and break the first time the internals changed. When you need an order, collect the keys into a slice and sort them.
- An empty slice is ready for
append:appendcreates the storage itself and returns a new slice. A map has noappend, and a write goes straight into storage that does not exist yet, so the program stops. A map is created withmakeor with a literal.
Sources
If you have found a mistake or a typo in this article, tell us about it
Comments (0)
Log in to leave a comment →
No comments yet. Be the first.