What a map is
A map is Go's built-in dictionary. It stores key: value pairs and finds a value by its key without scanning. The type map[string]int reads left to right as "keys are strings, values are ints". The simplest way to create one is a map literal, which creates and fills the map in one statement:
scores := map[string]int{"Alice": 92, "Bob": 77}
fmt.Println(scores["Alice"]) // 92
scores["Bob"] = 80 // update a value
Map keys must be comparable with ==, so strings, numbers and booleans work, while slices don't.
Nil maps: reading is fine, writing panics
A map that is only declared is nil:
var m map[string]int
fmt.Println(m["x"], len(m), m == nil) // 0 0 true
m["x"] = 1 // panic: assignment to entry in nil map
Reading from a nil map works and behaves like reading from an empty one. Writing stops the program with panic: assignment to entry in nil map. Create maps with a literal or with make(map[string]int), and the problem never appears.
The trap: a missing key gives the zero value
In many languages, asking a dictionary for a key it doesn't have raises an error or returns a special "nothing" value. Go does neither. A lookup of a missing key returns the zero value of the value type: 0 for numbers, "" for strings, false for bools.
fmt.Println(scores["Carol"]) // 0
That 0 is ambiguous. Either Carol is not in the map, or she is there with a score of zero. The lookup alone can't tell you which.
The comma-ok idiom
A map read can return a second value, a bool that is true only when the key was present:
score, ok := scores["Carol"]
if !ok {
fmt.Println("Carol is not in the map")
}
The second variable is called ok by convention, and you will see v, ok := m[key] throughout real Go code. When you only need the check inside an if, declare the variables in its header so they stay scoped to the statement:
if v, found := scores["Bob"]; found {
fmt.Println("Bob has", v)
}
A key stored with the zero value still reports ok as true. After scores["Dan"] = 0, the lookup d, ok := scores["Dan"] gives 0 true, while Carol gives 0 false. That is exactly the difference the idiom exists to reveal.
The exercise, step by step
- Create the map with a literal holding Alice and Bob. The literal makes sure it is never nil.
- Look up Alice with two results:
aliceScore, ok := scores["Alice"]. - Print the label, the score, the label and
okas four arguments tofmt.Println. It puts a space between arguments, so the labels need no trailing space. - Look up Carol the same way, into
carolScoreandok. Using:=withokagain is legal becausecarolScoreon the left is new. Go reports an error only when a:=declares no new variable at all, and hereokis simply assigned. - Print both values again. Carol gives the zero value and
false.
The solution in Go
scores := map[string]int{"Alice": 92, "Bob": 77}
aliceScore, ok := scores["Alice"]
fmt.Println("aliceScore =", aliceScore, "ok =", ok)
carolScore, ok := scores["Carol"]
fmt.Println("carolScore =", carolScore, "ok =", ok)
The output is:
aliceScore = 92 ok = true
carolScore = 0 ok = false
Why the idiom is more than a style rule
The zero-value behaviour is convenient as well as risky. A counter map needs no setup, because a missing key reads as 0 and the increment writes back a real entry:
counts := map[string]int{}
counts["a"]++
counts["a"]++
fmt.Println(counts) // map[a:2]
Here the zero value is exactly what you want, so no check is needed. The comma-ok form is for the cases where "absent" and "zero" mean different things: a score of zero against a student who never took the test, or a setting that is switched off against one that was never configured. When both cases would be handled the same way, the plain lookup is enough.
Deleting also follows the "no surprises" style. delete(scores, "Bob") removes the entry, and deleting a key that isn't there does nothing and doesn't panic. Afterwards the comma-ok lookup for that key returns 0 false.
Common mistakes
- Treating 0 as "not found". This is wrong whenever zero is a legal value. Check
okinstead. - Unused variables. Go refuses to compile a variable that is declared and never used. If you only care about presence, write
_, ok := scores["Carol"]. - Writing to a nil map.
var m map[string]intfollowed by an assignment panics at run time. - Relying on iteration order. Looping over a map with
rangevisits the entries in an unspecified order that can differ between runs.
Key takeaways
- A missing key returns the zero value, never an error.
v, ok := m[key]tells a missing key apart from a stored zero.- Create maps with a literal or
make. A nil map can be read but not written.