Maps
Reviewed & published by Brayan K
A map is Go's built-in key/value lookup table — the data structure you reach for whenever you need to find something by name rather than by position. You'll learn to create maps, read and write entries, safely check whether a key exists with the "comma ok" idiom, delete keys, and tally data with the classic counting pattern.
Part of the free Go course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
- Create maps with make and with literals
- Read, write and delete entries
- Know that a missing key returns the zero value
- Use the comma-ok idiom to test existence
- Range over a map (and why you must sort for order)
- Build the count/tally pattern with a map
1️⃣ Creating & Accessing
A map's type is map[KeyType]ValueType — for example map[string]int. Build one with make or a literal, then read and write entries with square brackets. A crucial Go quirk: reading a key that isn't there does not error — it returns the value type's zero value (0, "", false). That's convenient, but it's why you need the comma-ok check below.
package main
import "fmt"
func main() {
// A map is a key -> value lookup table. Type: map[KeyType]ValueType.
// make builds an empty, ready-to-use map.
ages := make(map[string]int)
ages["Alice"] = 30
ages["Bob"] = 25
fmt.Println(ages["Alice"]) // 30
// Map literal — set entries up front.
capitals := map[string]string{
"France": "Paris",
"Japan": "Tokyo",
}
fmt.Println(capitals["Japan"]) // Tokyo
// Reading a MISSING key returns the value type's ZERO value,
// never an error — so this is 0, not a crash.
fmt.Println("missing:", ages["Nobody"]) // missing: 0
fmt.Println("entries:", len(ages)) // entries: 2
}2️⃣ comma-ok & delete
Because a missing key returns a zero value, you can't tell "the value really is 0" from "the key isn't here" by reading alone. The comma-ok form solves this: value, ok := m[key] gives you the value and a boolean that's true only when the key exists. Remove an entry with the built-in delete(m, key), which is safe even if the key was never there.
package main
import "fmt"
func main() {
inventory := map[string]int{
"apples": 5,
"bananas": 0, // we HAVE a bananas entry, the count is just 0
}
// The "comma ok" form tells you whether a key EXISTS,
// separate from its value. This distinguishes "0 count"
// from "not in the map at all".
count, ok := inventory["bananas"]
fmt.Println("bananas:", count, "present?", ok) // bananas: 0 present? true
_, ok2 := inventory["cherries"]
fmt.Println("cherries present?", ok2) // cherries present? false
// delete removes a key (safe even if the key is absent).
delete(inventory, "apples")
fmt.Println("after delete:", len(inventory)) // after delete: 1
}3️⃣ Ranging & the Count Pattern
You range over a map to get keys and values, but remember the order is randomized on every run — never depend on it. For stable output, collect the keys into a slice and sort them. The single most common map idiom is counting: counts[w]++ works even for a brand-new key because the missing value starts at the zero value 0.
package main
import (
"fmt"
"sort"
)
func main() {
// Classic use: count occurrences of each word.
words := []string{"go", "rust", "go", "go", "rust"}
counts := map[string]int{} // empty literal, same as make
for _, w := range words {
counts[w]++ // missing key starts at 0, then +1
}
// counts is now map[go:3 rust:2]
// Map iteration order is RANDOM. For stable output, sort the keys.
keys := make([]string, 0, len(counts))
for k := range counts {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Printf("%s: %d\n", k, counts[k])
}
}Your turn — build a small price list and read one back.
package main
import "fmt"
func main() {
// 🎯 YOUR TURN — replace each ___ then run it.
// 1) Make an empty map from string to int (a price list)
prices := ___(map[string]int) // 👉 make
// 2) Add two products
prices["pen"] = 2
prices["pad"] = 5
// 3) Read the price of a pad
fmt.Println("pad costs", prices[___]) // 👉 "pad"
// ✅ Expected output: pad costs 5
}Now use the comma-ok idiom to check whether a user is registered.
package main
import "fmt"
func main() {
// 🎯 YOUR TURN — finish the existence check then run it.
users := map[string]bool{"ada": true}
if _, ___ := users["ada"]; ok { // 👉 ok (the "comma ok" variable)
fmt.Println("ada is registered")
} else {
fmt.Println("no such user")
}
// ✅ Expected output: ada is registered
}🧩 Reorder Challenge
These lines count a key twice, but they're scrambled. Put them in order so the program prints 2 (assume a func main wraps them).
m["a"]++
fmt.Println(m["a"])
m := map[string]int{}
m["a"]++- m := map[string]int{}
- fmt.Println(m["a"])
Why: the map must exist before any key can be incremented. The first m["a"]++ turns the missing-key zero into 1; the second makes it 2. Printing m["a"] last yields 2. Both increments must precede the print.
Pro Tips
- 💡 Use comma-ok whenever a zero value is ambiguous. "Is this user banned?" needs to tell "false" from "not in the map".
- 💡 counts[k]++ is idiomatic — no need to check the key first, since a missing key reads as 0.
- 💡 Sort the keys for deterministic output. Tests and logs that depend on map order are flaky.
- 💡 Keys must be comparable. Strings, numbers, bools and structs work; slices and maps cannot be keys.
Common Errors (and the fix)
- "assignment to entry in nil map" — you declared var m map[string]int but never made it. A nil map can be read but not written. Use make or a literal first.
- Treating a 0 as "missing" — a real entry can legitimately be 0. Use the comma-ok form to distinguish absent from zero.
- Relying on iteration order — Go randomizes it. Collect keys into a slice and sort them for stable output.
- "invalid map key type []string" — slices aren't comparable, so they can't be keys. Use a string or an array instead.
📋 Quick Reference
| Task | Syntax |
|---|---|
| Make | make(map[string]int) |
| Literal | map[string]int{"a": 1} |
| Set / get | m["a"] = 1; x := m["a"] |
| Exists? | v, ok := m["a"] |
| Delete | delete(m, "a") |
| Count entries | len(m) |
🧠 Quick Recall
Predict the output before you reveal the answer.
0 — reading a missing key returns the value type's zero value, which is 0 for int. No error, no panic.
m := map[string]int{"a": 0}; _, ok := m["a"]; fmt.Println(ok)
true — the key "a" exists (its value just happens to be 0), so the comma-ok boolean is true.
m := map[string]int{}; m["go"]++; m["go"]++; fmt.Println(m["go"])
2 — the first increment takes the implicit 0 to 1, the second to 2. This is the counting idiom in action.
Frequently Asked Questions
Q: Why does reading a missing key not crash?
Go returns the value type's zero value instead, which keeps lookup code simple. Use the comma-ok form (v, ok := m[k]) when you must know whether the key actually exists.
Q: Can I use any type as a key?
Only comparable types: strings, numbers, booleans, arrays, and structs of comparable fields. Slices, maps, and functions can't be keys because they aren't comparable with ==.
Q: How do I get a sorted list of keys?
Range the map to collect keys into a slice, then call sort.Strings (or sort.Ints). Iterate that sorted slice for deterministic output.
Mini-Challenge: Tally Letters
Range over the word's runes and count each letter into a map[rune]int, then print the tally for the letter 'l'. Write it yourself and match the example output.
package main
import "fmt"
func main() {
// 🎯 MINI-CHALLENGE: Tally letters
// Count how many times each letter appears in the word below.
// Range over the string (runes), use a map[rune]int, and
// print the count for 'l'.
//
// ✅ Example output (for "hello"): l appears 2 times
word := "hello"
// your code here
}🎉 Lesson Complete!
- ✅ Maps are map[Key]Value lookup tables, built with make or a literal
- ✅ A missing key returns the value's zero value — never an error
- ✅ Use comma-ok (v, ok := m[k]) to tell "absent" from "zero"
- ✅ delete(m, k) removes an entry; len(m) counts them
- ✅ Iteration order is random — sort the keys for stable output; m[k]++ tallies cleanly
Practice quiz
What is the type syntax for a Go map from string keys to int values?
- map<string,int>
- map[string]int
- [string]int
- dict[string]int
Answer: map[string]int. Go maps are written map[KeyType]ValueType, e.g. map[string]int.
What does a map return when you read a key that is absent?
- An error
- nil
- The value type's zero value
- A panic
Answer: The value type's zero value. A missing key returns the value type's zero value — never an error.
Which form distinguishes 'absent' from 'present but zero'?
- v := m[k]
- v, ok := m[k]
- m.Get(k)
- len(m[k])
Answer: v, ok := m[k]. The comma-ok form v, ok := m[k] sets ok=false when the key is missing.
How do you remove an entry from a map?
- remove(m, k)
- m.delete(k)
- delete(m, k)
- m[k] = nil
Answer: delete(m, k). The built-in delete(m, k) removes the entry for key k.
What does len(m) return for a map?
- The capacity
- The number of key/value pairs
- Always 0
- The size in bytes
Answer: The number of key/value pairs. len(m) counts how many key/value pairs the map currently holds.
What is the iteration order when you range over a map?
- Insertion order
- Sorted by key
- Random / unspecified
- Reverse insertion
Answer: Random / unspecified. Map iteration order is randomized; sort the keys yourself for stable output.
Which built-in creates an empty, usable map?
- new(map)
- make(map[string]int)
- map.New()
- alloc()
Answer: make(map[string]int). make(map[K]V) creates an initialized map ready for writes.
What does m[k]++ do when k is not yet in the map?
- Panics
- Starts from the zero value, so becomes 1
- Does nothing
- Returns an error
Answer: Starts from the zero value, so becomes 1. Reading a missing int key yields 0, so m[k]++ cleanly sets it to 1 — handy for tallies.
What happens if you write to a nil map?
- It is created
- It panics
- It is ignored
- It returns false
Answer: It panics. Writing to a nil map panics; initialize with make or a literal first.
To print map entries in a stable order, you should:
- Use a special flag
- Collect keys, sort them, then range over keys
- Nothing — it is already sorted
- Convert to a slice automatically
Answer: Collect keys, sort them, then range over keys. Gather the keys into a slice, sort them, then iterate in that order.