Maps Operations & Patterns
Maps Operations & Patterns
A map in Go is an unordered collection of key-value pairs. Maps are reference types — assigning a map to another variable or passing it to a function shares the underlying data.
Creating Maps
// With make — preferred when size is unknown
m := make(map[string]int)
// Map literal — preferred when values are known upfront
m := map[string]int{"a": 1, "b": 2}Reading, Writing and Deleting
m["key"] = 42 // write / update
v := m["key"] // read (returns zero value if key absent)
delete(m, "key") // remove entry (no-op if key absent)Checking Key Existence — the comma-ok idiom A plain read returns the zero value for missing keys, which is ambiguous. Use the two-value form to distinguish:
v, ok := m["key"]
if ok {
fmt.Println("found:", v)
} else {
fmt.Println("not found")
}Iterating with range Map iteration order is intentionally randomised on every run:
for k, v := range m {
fmt.Println(k, v)
}Maps of Slices Group values under a key by storing a slice as the value type:
groups := make(map[string][]string)
groups["fruit"] = append(groups["fruit"], "apple")This is safe because appending to a nil slice is valid.
Word Count Pattern A classic use-case — count occurrences of each word:
counts := make(map[string]int)
for _, word := range words {
counts[word]++
}The expression counts[word]++ leverages the zero-value default: a missing key returns 0, then increments to 1.
Sorting Map Keys for Deterministic Output When you need predictable output (tests, reports), extract and sort the keys:
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Println(k, m[k])
}nil Map vs Empty Map
var m map[string]int— nil map; reads return zero values safely, but writes panicm := make(map[string]int)— empty map; reads and writes are both safe
Always initialise a map before writing to it. A common guard pattern:
if m == nil {
m = make(map[string]int)
}Maps cannot hold maps as values that reference themselves (no cycles), and map keys must be comparable types (no slices, maps, or functions as keys).
Code Examples
package main
import (
"fmt"
"sort"
"strings"
)
func wordCount(text string) map[string]int {
counts := make(map[string]int)
words := strings.Fields(text) // splits on any whitespace
for _, w := range words {
w = strings.ToLower(w)
w = strings.Trim(w, ".,!?;:")
counts[w]++
}
return counts
}
func main() {
text := "go is fast, go is simple, and go is fun!"
counts := wordCount(text)
// Sort keys for deterministic output
keys := make([]string, 0, len(counts))
for k := range counts {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Printf("%-10s %d\n", k, counts[k])
}
// comma-ok to check existence
if n, ok := counts["go"]; ok {
fmt.Println("\n'go' appears", n, "times")
}
}counts[w]++ exploits the zero-value default. Sorting keys after iteration gives deterministic output regardless of Go's randomised map ordering.
package main
import (
"fmt"
"sort"
)
type Employee struct {
Name string
Department string
}
func groupByDept(employees []Employee) map[string][]string {
groups := make(map[string][]string)
for _, e := range employees {
groups[e.Department] = append(groups[e.Department], e.Name)
}
return groups
}
func main() {
staff := []Employee{
{"Alice", "Engineering"},
{"Bob", "Marketing"},
{"Carol", "Engineering"},
{"Dave", "Marketing"},
{"Eve", "Engineering"},
}
byDept := groupByDept(staff)
// Print in sorted department order
depts := make([]string, 0, len(byDept))
for d := range byDept {
depts = append(depts, d)
}
sort.Strings(depts)
for _, dept := range depts {
fmt.Printf("%s: %v\n", dept, byDept[dept])
}
// Delete a department entry
delete(byDept, "Marketing")
fmt.Println("\nAfter deleting Marketing:", byDept)
}Appending to a nil slice value in a map is valid; Go returns the zero value (nil slice) first, then append creates the backing array. delete removes the key entirely.
package main
import "fmt"
func safeMerge(dst, src map[string]int) map[string]int {
if dst == nil {
dst = make(map[string]int)
}
for k, v := range src {
dst[k] += v
}
return dst
}
func main() {
// nil map — reads are safe, writes panic
var scores map[string]int
fmt.Println("nil map read:", scores["alice"]) // 0, no panic
// This would panic:
// scores["alice"] = 10
// Safe initialisation before writing
scores = make(map[string]int)
scores["alice"] = 10
scores["bob"] = 20
extra := map[string]int{"alice": 5, "carol": 15}
merged := safeMerge(scores, extra)
fmt.Println("merged:", merged)
// Map is a reference type
ref := merged
ref["alice"] = 999
fmt.Println("merged after ref mutation:", merged["alice"]) // 999
}Reading a nil map is safe and returns zero values. Writing to a nil map panics at runtime — always initialise with make. Because maps are reference types, assigning to ref also mutates merged.
Quick Quiz
1. What does `v, ok := m["key"]` do when the key is absent?
2. Why is iterating over a map with range non-deterministic in Go?
3. What happens when you write to a nil map?
4. Which types CANNOT be used as map keys in Go?
Was this lesson helpful?