Go

Why does preallocating slices and maps matter? What does this print?

Question 395MediumGo 1.22 to 1.25
func main() {
    s := make([]int, 0, 2)
    a := append(s, 1)
    b := append(s, 2)
    fmt.Println(a[0], b[0], len(s), cap(s))

    var grow []int
    prev := -1
    for i := range 2000 {
        grow = append(grow, i)
        if cap(grow) != prev {
            prev = cap(grow)
        }
    }
    _ = prev
}

Prints 2 2 0 2. a and b share s's backing array because it had spare capacity; the second append overwrote index 0. This aliasing is a classic bug when you append to a slice you don't own — use slices.Clip(s) or a full slice expression s[:len(s):len(s)] to force a copy on append.

Performance side: appending to an unsized slice reallocates and copies whenever capacity is exceeded — doubling below 256 elements, then a smooth transition toward ~1.25x growth (Go 1.18+ formula newcap += (newcap + 3*256) / 4), with the result rounded up to a malloc size class. For n known up front:

out := make([]Result, 0, len(inputs)) // one allocation
for _, in := range inputs {
    out = append(out, process(in))
}
idx := make(map[string]int, len(inputs)) // avoids incremental rehash/growth

Maps are worse: growth means rehashing entries into new buckets (Go 1.24's Swiss-table maps split into tables of at most 1024 slots and grow one table at a time, which bounds the pause, but presizing still avoids all of that work). Gotcha: make([]T, n) vs make([]T, 0, n) — using the former then append leaves n zero values at the front. Also, maps never shrink: deleting all keys keeps the bucket memory; recreate the map to release it.

More on Performance, Profiling & Testing

All 38 Performance, Profiling & Testing questions