A map is Go’s built-in key-value store (called a “dictionary” or “hash” in other languages). Most map operations are built into the language — you don’t need a package for them. But the standard maps package (Go 1.21+) adds a few extras: cloning, comparing, filtering.
This page covers both the built-in map operations and the maps package, end to end.
import "maps" // only needed for the package functions below
At a glance
| Need to… | How |
|---|---|
| Create a map | make(map[K]V) or map[K]V{} |
| Insert / update | m[key] = value |
| Look up | v := m[key] (returns zero value if missing) |
| Check if key exists | v, ok := m[key] |
| Delete | delete(m, key) |
| Length | len(m) |
| Iterate | for k, v := range m (order is random!) |
| Copy a map | maps.Clone(m) |
| Compare two maps | maps.Equal(a, b) |
| Filter in place | maps.DeleteFunc(m, func) |
| Get all keys / values | maps.Keys(m) / maps.Values(m) |
Built-in operations
Creating a map
// Make an empty map
ages := make(map[string]int)
// Make with a capacity hint (faster for big maps)
ages := make(map[string]int, 1000)
// Make with values
ages := map[string]int{
"Alice": 30,
"Bob": 22,
"Carol": 28,
}
// Nil map — declared but not initialized
var ages map[string]int
// Reading from a nil map is fine (returns zero value)
// Writing to a nil map PANICS
The capacity hint isn’t a limit — it’s a starting size. Maps grow automatically. The hint just avoids early reallocations.
Insert, update, delete
ages["Dave"] = 40 // insert
ages["Alice"] = 31 // update (same syntax)
delete(ages, "Bob") // remove; no-op if key doesn't exist
Lookup — and the comma-ok idiom
A plain lookup returns the zero value if the key is missing, with no way to tell “missing” from “present with zero value”:
age := ages["Eve"] // 0 if Eve isn't there — but also 0 if Eve is 0 years old
The comma-ok form distinguishes them:
age, ok := ages["Eve"]
if !ok {
fmt.Println("Eve isn't in the map")
} else {
fmt.Println("Eve's age:", age)
}
This is one of the most-asked Go idioms in interviews. Memorize the shape.
Iteration — and the random-order gotcha
for name, age := range ages {
fmt.Println(name, age)
}
Map iteration order is randomized by design. Each
rangemay visit keys in a different order, even within the same run. If you need a stable order, sort the keys yourself:keys := make([]string, 0, len(ages)) for k := range ages { keys = append(keys, k) } slices.Sort(keys) for _, k := range keys { fmt.Println(k, ages[k]) }
Deleting during iteration is safe
Unlike some languages, you can delete from a map while iterating over it without breaking the loop:
for name, age := range ages {
if age < 18 {
delete(ages, name) // safe
}
}
Adding during iteration is unsafe — the new key may or may not be visited; behavior is undefined. Don’t.
The maps package (Go 1.21+)
maps.Clone — independent copy
a := map[string]int{"x": 1, "y": 2}
b := maps.Clone(a)
b["x"] = 99
// a["x"] is still 1
b := adoes NOT copy. Both variables point to the same underlying map.maps.Clonegives you a real copy.
maps.Copy — merge into an existing map
Copies all entries from src into dst, overwriting on collision.
dst := map[string]int{"a": 1}
src := map[string]int{"b": 2, "a": 99}
maps.Copy(dst, src)
// dst = {"a": 99, "b": 2}
maps.Equal — element-by-element equality
a := map[string]int{"x": 1, "y": 2}
b := map[string]int{"y": 2, "x": 1} // same content, different insert order
maps.Equal(a, b) // true
Order doesn’t matter. Two maps are equal if they have the same keys mapping to the same values.
maps.DeleteFunc — filter in place
Removes every entry where the predicate returns true:
maps.DeleteFunc(ages, func(name string, age int) bool {
return age < 18
})
// removes everyone under 18
maps.Keys and maps.Values — extract as a slice
Note: in Go 1.23+ these return iterators (
iter.Seq). In Go 1.21 and 1.22 they returned[]K/[]Vslices directly. The patterns below cover both.
In Go 1.23+ (iterator form):
import "slices"
keys := slices.Collect(maps.Keys(ages)) // []string
values := slices.Collect(maps.Values(ages)) // []int
In Go 1.21 / 1.22 (slice form):
keys := maps.Keys(ages) // []string
values := maps.Values(ages) // []int
Either way, the order is random — slice the keys and sort them if you need a stable order.
Common interview patterns
Map as a set
Go has no built-in set type. The idiomatic substitute is map[T]struct{} — struct{} takes zero memory.
seen := make(map[string]struct{})
seen["apple"] = struct{}{}
seen["banana"] = struct{}{}
_, exists := seen["apple"] // true
_, exists = seen["cherry"] // false
delete(seen, "apple")
Using
map[string]boolworks too and is sometimes clearer. Thestruct{}version is just slightly more memory-efficient.
Count occurrences
words := []string{"go", "rust", "go", "python", "go", "rust"}
counts := make(map[string]int)
for _, w := range words {
counts[w]++
}
// counts = {"go": 3, "rust": 2, "python": 1}
This works because reading a missing key returns the zero value (0 for int), and counts[w]++ is shorthand for counts[w] = counts[w] + 1.
Find the most frequent value
var top string
maxCount := 0
for word, count := range counts {
if count > maxCount {
top = word
maxCount = count
}
}
// top = "go", maxCount = 3
Group items by a key
type Person struct {
Name string
City string
}
people := []Person{
{"Alice", "NYC"},
{"Bob", "LA"},
{"Carol", "NYC"},
}
byCity := make(map[string][]Person)
for _, p := range people {
byCity[p.City] = append(byCity[p.City], p)
}
// byCity = {
// "NYC": [{Alice NYC} {Carol NYC}],
// "LA": [{Bob LA}],
// }
This idiom — appending to a slice stored in a map — works even though byCity[city] is nil on first access, because append accepts a nil slice.
Two-sum (classic interview problem)
Given a slice of integers and a target, find two indices whose values sum to the target:
func twoSum(nums []int, target int) (int, int) {
seen := make(map[int]int) // value -> index
for i, n := range nums {
if j, ok := seen[target-n]; ok {
return j, i
}
seen[n] = i
}
return -1, -1
}
The map turns this from O(n²) (nested loops) into O(n) (one pass). Classic interview answer.
Quick rules of thumb
- Create with
makewhen you’ll add lots of entries — pass a capacity hint if you know roughly how many. - Use comma-ok (
v, ok := m[k]) when “missing” matters — plain lookup hides the difference. - Map iteration order is random — sort keys if you need stability.
- Need a set? →
map[T]struct{} - Need to copy a map? →
maps.Clone, neverb := a - Writing to a nil map? → Panic. Always
makefirst.
Common mistakes
b := athinking it copies the map — both point to the same map. Usemaps.Clone.- Writing to a nil map — panics.
var m map[string]intthenm["x"] = 1blows up. - Relying on iteration order — Go intentionally randomizes it. Tests that depend on order are flaky.
if m[key] != 0to check existence — fails when the value is genuinely zero. Use comma-ok.- Using
len(m) == 0to check for nil — works (lenis safe on nil maps), butm == nilis clearer if that’s what you actually mean. - Iterating over
maps.Keysand expecting alphabetical order — random. Always sort if you care.