Go Maps
Overview
A map is Go's built-in hash map — an unordered collection of key-value pairs with O(1) average lookup, insert, and delete. Keys must be a comparable type (anything you can use == on). Maps are reference types — passing a map to a function gives the function access to the same underlying data.
Python equivalent: dict. JavaScript equivalent: object / Map.
Creating Maps
make — preferred
Always use make (or a literal) before writing. A nil map panics on write.
m := make(map[string]int) // empty, ready to use
m := make(map[string]int, 100) // pre-allocated for ~100 entries (hint, not limit)
Map literal
Define and populate in one shot.
ages := map[string]int{
"Alice": 30,
"Bob": 25,
"Carol": 35,
}
// Empty literal — same as make, but less idiomatic
m := map[string]int{}
CRUD Operations
Insert / Update
Same syntax — if the key exists it's overwritten, otherwise it's inserted.
m := make(map[string]int)
m["alice"] = 25 // insert
m["alice"] = 26 // update — no error, just overwrites
Read
Reading a missing key returns the zero value for the value type — not an error. Use the comma-ok idiom to distinguish "key is 0" from "key doesn't exist".
age := m["alice"] // 26
age = m["unknown"] // 0 — zero value, NOT an error
Delete
delete is a no-op if the key doesn't exist — safe to call unconditionally.
delete(m, "alice") // removes alice
delete(m, "nobody") // no-op — no panic
Length
fmt.Println(len(m)) // number of key-value pairs
Comma-ok Idiom — Check Key Existence
The two-value form of a map read returns a boolean ok that is true only if the key was present. This is the idiomatic way to tell if a key exists.
m := map[string]int{"alice": 30}
// Two-value form
age, ok := m["alice"]
if ok {
fmt.Printf("Alice is %d years old\n", age)
} else {
fmt.Println("alice not found")
}
// Inline — scope ok to the if block
if age, ok := m["bob"]; ok {
fmt.Println("Bob's age:", age)
}
// Check-only (discard value)
_, exists := m["carol"]
fmt.Println(exists) // false
Iterating Over Maps
Use range. Order is randomised on every run — this is by design (prevents relying on insertion order).
scores := map[string]int{"Alice": 95, "Bob": 87, "Carol": 92}
// Key + value
for name, score := range scores {
fmt.Printf("%s: %d\n", name, score)
}
// Keys only
for name := range scores {
fmt.Println(name)
}
Sorted iteration
import "sort"
keys := make([]string, 0, len(scores))
for k := range scores {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Printf("%s: %d\n", k, scores[k])
}
Map of Slices
A common pattern — each key maps to a list of values.
// Group people by city
cityPeople := make(map[string][]string)
cityPeople["Mumbai"] = append(cityPeople["Mumbai"], "Alice")
cityPeople["Mumbai"] = append(cityPeople["Mumbai"], "Bob")
cityPeople["Delhi"] = append(cityPeople["Delhi"], "Carol")
fmt.Println(cityPeople["Mumbai"]) // [Alice Bob]
// Reading a missing key returns nil — append to nil is safe
cityPeople["Pune"] = append(cityPeople["Pune"], "Dave") // ✅
Map of Maps (Nested Maps)
// Config: section → key → value
config := map[string]map[string]string{
"database": {
"host": "localhost",
"port": "5432",
},
"server": {
"host": "0.0.0.0",
"port": "8080",
},
}
fmt.Println(config["database"]["host"]) // localhost
Set Pattern — map[K]struct{}
When you only care about whether a key exists (not its value), use struct{} as the value type — it occupies zero bytes.
// Unique string set
seen := make(map[string]struct{})
words := []string{"go", "is", "fast", "go", "is", "great"}
for _, w := range words {
seen[w] = struct{}{}
}
fmt.Println(len(seen)) // 4 — duplicates removed
// Check membership
_, exists := seen["go"]
fmt.Println(exists) // true
// Alternatively — bool values are simpler to read
visited := make(map[int]bool)
visited[42] = true
if visited[42] { fmt.Println("been here") }
Nil Map Behaviour
A nil map behaves like an empty map for reads — but panics on write. Always initialise before writing.
var m map[string]int // nil
// Read — safe, returns zero value
v := m["key"] // 0, no panic
// Write — PANICS
m["key"] = 1 // panic: assignment to entry in nil map
// Fix
m = make(map[string]int)
m["key"] = 1 // ✅
Maps are Reference Types
When you assign a map to another variable or pass it to a function, both refer to the same underlying data. There's no implicit copy.
a := map[string]int{"x": 1}
b := a // b and a point to the SAME map
b["x"] = 99
fmt.Println(a["x"]) // 99 — a is also changed
// To get an independent copy, copy manually
c := make(map[string]int, len(a))
for k, v := range a {
c[k] = v
}
Maps Cheatsheet
| Operation | Code |
|---|---|
| Create | make(map[K]V) |
| Create + init | map[K]V{k: v, ...} |
| Insert / Update | m[k] = v |
| Read | v := m[k] |
| Check existence | v, ok := m[k] |
| Delete | delete(m, k) |
| Length | len(m) |
| Iterate | for k, v := range m {} |
| Set (key only) | map[K]struct{} |
Gotchas
| Gotcha | Detail |
|---|---|
| Write to nil map | Panics — always initialise with make or a literal |
| Zero value on missing key | m["x"] returns 0/"" — not an error. Use comma-ok to distinguish |
| Iteration order | Random by design — sort keys if order matters |
| Maps are reference types | No implicit copy on assignment or function call |
| Map key must be comparable | Slices, maps, and functions cannot be map keys |
| Not concurrency-safe | Concurrent reads are fine; concurrent read+write is a data race. Use sync.Map or a mutex |