Most confusing slice bugs come from not knowing when append reuses memory and when it copies.
A slice header is three words: a pointer into a backing array, a length and a capacity. Passing a slice copies the header, not the elements, so the callee can change elements the caller sees — but if it appends, the caller's header still has the old length.
append(s, x) does one of two things:
- If
len(s) < cap(s), it writesxinto the existing array and returns a header with a longer length. Any other slice sharing that array sees the write. - Otherwise it allocates a new, larger array (roughly doubling for small slices, growing more gently for large ones), copies, and returns a header pointing to the new array. From then on, old and new slices are independent.
That's why this bug is so easy to write: take a := base[:2] from a slice of capacity 4, append(a, 99) — and you have silently overwritten base[2]. Defences:
- Use a full slice expression
base[:2:2]to cap the capacity, forcing the next append to copy. - Clone with
slices.Clonebefore handing a slice to code that may append. - If a function appends, return the new slice (
s = add(s, x)) asappenditself does.
When you know the final size, preallocate with make([]T, 0, n) to avoid repeated grow-and-copy cycles.
A small sub-slice of a huge array keeps the whole array alive for the garbage collector. When you keep a few bytes of a large buffer, copy them out instead of slicing.
Example
package main
import "fmt"
func main() {
base := make([]int, 4, 4) // [0 0 0 0]
a := base[:2] // len 2, cap 4
a = append(a, 99) // fits: writes into base[2]
fmt.Println(base) // [0 0 99 0]
b := base[:2:2] // len 2, cap 2
b = append(b, 7) // no room: copies to a new array
fmt.Println(base) // [0 0 99 0] (unchanged)
fmt.Println(b) // [0 0 7]
out := make([]int, 0, 3) // preallocate capacity
for i := range 3 {
out = append(out, i)
}
fmt.Println(len(out), cap(out)) // 3 3
}Remember
- append writes in place while there's capacity, otherwise reallocates
s[lo:hi:max]limits capacity so append must copy- Preallocate with
make([]T, 0, n)when the size is known
Watch out for
- Appending to a sub-slice and unknowingly overwriting the parent's elements.