Go Slice Growth

Go stopped doubling slice capacities in 1.18, and the capacity you get is not the one the growth formula asked for. This shows both: the formula's answer, the allocator's rounding up to a size class, and what it all costs — with every number read out of the Go toolchain rather than from a blog post.

Nothing you enter is transmitted. There is no endpoint behind this page: the Go that computes the progression is compiled to WebAssembly and runs in this tab, so your numbers never leave the browser.

Start from one of these

    Go does not double the capacity

    It did until Go 1.18. It does not now, and a great many pages still say otherwise.

    Below a threshold, append doubles. Above it the growth factor falls toward 1.25, and it falls on a curve rather than a step: each growth adds a quarter of the current capacity plus a fixed amount, so the ratio slides from 2 down toward 1.25 across several growths rather than switching at one. That is why the ratio column in the table above is full of numbers like 1.63 and 1.38 — values that appear in no blog post because nobody expects them.

    And the capacity you get is not the capacity it asked for

    This is the half almost nobody accounts for. The growth formula produces a number of elements; the runtime multiplies by the element size to get bytes, and then hands that to the allocator — which does not allocate arbitrary sizes. It rounds up to one of a fixed set of size classes. Those spare bytes are not wasted: the slice gets them as capacity.

    So a []byte that the formula says should have capacity 1 comes back with capacity 8, because 8 bytes is the smallest class. A 24-byte struct behaves differently from a 32-byte one for the same reason. The table above shows what the formula asked for and what you actually got, in separate columns, because the difference between them is the point.

    Where these numbers come from

    Not from memory, and not from a table typed in by hand. The doubling threshold and the two coefficients of the 1.25× step are read out of runtime/slice.go in the Go toolchain installed on the build machine; the size classes and their lookup tables come from internal/runtime/gc. That package cannot be imported — it is internal — so the numbers are extracted by a generator and committed, and a test re-derives every one of them from the standard library on every run. A Go upgrade turns the build red rather than leaving this page quietly describing the previous release.

    The model is also checked against reality: the test suite grows real slices with the real runtime, in the same process, and requires every capacity to match.

    One thing this does not model

    A slice the compiler can prove never escapes is not grown by runtime.growslice at all — the compiler generates its own growth against a stack buffer, and the capacities differ. The progression here is the heap one, which is what you get whenever the slice is returned, stored, or passed somewhere that outlives the function. That is the case worth reasoning about, and it is the one this models.

    The size classes

    The other tools