What does this print? (map of slices built from a shared base)
base := make([]int, 0, 4)
m := map[string][]int{}
m["a"] = append(base, 1)
m["b"] = append(base, 2)
fmt.Println(m["a"], m["b"])
m["a"] = append(m["a"], 10)
fmt.Println(m["a"], m["b"], m["b"][:2])
Output:
[2] [2]
[2 10] [2] [2 10]
base has len 0 and cap 4, so both appends write to index 0 of the same backing array. The second one overwrites the first, and m["a"] "becomes" [2]. The map stores slice headers, so both entries are windows into one array. The next append(m["a"], 10) still fits in the capacity and writes index 1. m["b"] has len 1, so it hides that write, but reslicing up to its cap shows it.
This happens in real code when a "template" slice, such as default headers, tags or a reusable buffer, is appended to for each key. It is the same aliasing bug as with two appends on one base slice, but it is harder to spot because the headers are spread across map entries.
// Fixes
m["a"] = append(slices.Clip(base), 1) // Clip sets cap = len, so append must allocate
m["b"] = append(base[:len(base):len(base)], 2)
m["c"] = append(slices.Clone(base), 3) // Clone returns a copy with no spare capacity shared with base
m[k] = append(m[k], x) is still the correct idiom for a map of slices. A missing key gives a nil slice, and append on nil allocates. The problem only appears when several entries start from the same array that has spare capacity.
More on Arrays, Slices, Maps & Strings
- Q71How do you implement a set in Go, and what maps package helpers exist?
- Q72What does this print before and after Go 1.22? (taking &v and capturing i in loops)
- Q74What does this print? (range over a nil pointer to an array, and range over an int)
- Q75How do range-over-func iterators work on slices and maps, and what happens if an iterator ignores yield's return value?
- Q76What is the unique package (Go 1.23), and how does it help with memory for repeated strings?