How does append grow a slice? Why is the resulting capacity sometimes not exactly double?
Question 42HardGo 1.22 to 1.25
When len + n > cap, append calls runtime.growslice. Since Go 1.20 the rule (nextslicecap) is:
- If the needed length is more than twice the old cap, use the needed length.
- If old cap < 256, double it.
- Otherwise grow by
(newcap + 3*256) / 4repeatedly. The factor moves smoothly from 2x down toward 1.25x for large slices.
The byte size is then rounded up to a malloc size class, so the final cap is often larger than the formula gives.
var s []int
prev := -1
for i := range 2000 {
s = append(s, i)
if cap(s) != prev {
fmt.Print(cap(s), " ")
prev = cap(s)
}
}
// 64-bit: 1 2 4 8 16 32 64 128 256 512 848 1280 1792 2560
x := append([]int(nil), 1, 2, 3, 4, 5)
fmt.Println(cap(x)) // 6, not 5: 40 bytes rounds up to the 48-byte size class
Gotchas: never write code that depends on exact capacities, because they depend on the Go version and element size. If you know the final size, preallocate with make([]T, 0, n) to avoid O(log n) reallocations and copies. Growth also leaves the old array for the GC, so repeated appends to large slices cause memory churn.
More on Arrays, Slices, Maps & Strings
- Q40What is a slice header, and what really gets passed when you pass a slice to a function?
- Q41What does this program print? (append inside a function with spare capacity)
- Q43What does this print? (two appends on the same base slice)
- Q44What is the full slice expression a[low:high:max], and when would you use it?
- Q45What is the difference between a nil slice and an empty slice? When does it matter?
- Q46Explain the semantics of the built-in copy. Is it safe with overlapping slices?