Go

What does this print? (map of slices built from a shared base)

Question 73HardGo 1.22 to 1.25
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

All 37 Arrays, Slices, Maps & Strings questions