Maps
A map is Go's built-in associative data structure — an unordered collection of key–value pairs. Maps are implemented as hash tables and provide O(1) average-case lookups, insertions, and deletions.
Creating Maps
Use a map literal or make:
package main
import "fmt"
func main() {
// Map literal
capitals := map[string]string{
"France": "Paris",
"Germany": "Berlin",
"Japan": "Tokyo",
}
fmt.Println(capitals["France"]) // Paris
// make
scores := make(map[string]int)
scores["Alice"] = 100
scores["Bob"] = 85
fmt.Println(scores) // map[Alice:100 Bob:85]
}CRUD Operations
package main
import "fmt"
func main() {
stock := map[string]int{
"apples": 50,
"bananas": 30,
}
// Read
fmt.Println(stock["apples"]) // 50
// Update
stock["apples"] = 45
// Create
stock["cherries"] = 100
// Delete
delete(stock, "bananas")
fmt.Println(stock) // map[apples:45 cherries:100]
}Checking Whether a Key Exists
A map lookup returns two values: the value and a boolean indicating whether the key was present. This is the idiomatic pattern:
package main
import "fmt"
func main() {
ages := map[string]int{
"Alice": 30,
"Bob": 25,
}
if age, ok := ages["Alice"]; ok {
fmt.Println("Alice's age:", age)
}
if _, ok := ages["Charlie"]; !ok {
fmt.Println("Charlie not found")
}
// Without ok: missing keys return the zero value
fmt.Println(ages["Charlie"]) // 0 — no error, but misleading
}Idiomatic Go: Always use the two-value form
val, ok := m[key]when the key's presence is meaningful. Relying on the zero value can hide bugs.
Nil Maps
A var declaration without initialisation produces a nil map. Reading from a nil map returns the zero value; writing to one panics:
package main
import "fmt"
func main() {
var m map[string]int
fmt.Println(m["key"]) // 0 — safe to read
fmt.Println(m == nil) // true
// m["key"] = 1 // would panic: assignment to entry in nil map
m = make(map[string]int)
m["key"] = 1
fmt.Println(m["key"]) // 1
}Iterating with range
Map iteration order is deliberately randomised in Go. Do not rely on it:
package main
import (
"fmt"
"sort"
)
func main() {
population := map[string]int{
"London": 9_000_000,
"Tokyo": 14_000_000,
"New York": 8_000_000,
}
// Collect and sort keys for deterministic output
keys := make([]string, 0, len(population))
for k := range population {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Printf("%s: %d\n", k, population[k])
}
}Maps of Slices
A common pattern is a map where each value is a slice — useful for grouping:
package main
import "fmt"
func groupByLength(words []string) map[int][]string {
groups := make(map[int][]string)
for _, w := range words {
groups[len(w)] = append(groups[len(w)], w)
}
return groups
}
func main() {
words := []string{"go", "is", "fun", "and", "fast"}
groups := groupByLength(words)
fmt.Println(groups[2]) // [go is]
fmt.Println(groups[3]) // [fun and]
fmt.Println(groups[4]) // [fast]
}Key Takeaways
- Maps are reference types: a function that receives a map can mutate the caller's map.
- Always use
val, ok := m[key]when you need to distinguish a missing key from the zero value. - Never write to a nil map — initialise first with
makeor a literal. - Map iteration order is non-deterministic; sort keys if you need a stable order.