Golang slices.Grow
last modified April 20, 2025
This tutorial explains how to use the slices.Grow
function in Go.
We'll cover slice capacity management with practical examples.
The slices.Grow function increases a slice's capacity to guarantee space for additional elements. It's part of Go's experimental slices package.
This function is useful when you need to append many elements efficiently. It minimizes memory allocations by pre-allocating capacity in advance.
Basic slices.Grow Example
The simplest use of slices.Grow
prepares a slice for future appends.
We specify how many additional elements we expect to add.
package main import ( "fmt" "slices" ) func main() { s := []int{1, 2, 3} fmt.Println("Before Grow - len:", len(s), "cap:", cap(s)) s = slices.Grow(s, 5) fmt.Println("After Grow - len:", len(s), "cap:", cap(s)) // Now we can append without reallocations s = append(s, 4, 5, 6, 7, 8) fmt.Println("After append - len:", len(s), "cap:", cap(s)) }
We grow the slice by 5 elements, then append 5 values. The capacity increases to accommodate future additions without multiple allocations.
Growing an Empty Slice
slices.Grow
works with empty slices. This example shows how to
pre-allocate capacity for a new slice.
package main import ( "fmt" "slices" ) func main() { var s []string fmt.Println("Before Grow - len:", len(s), "cap:", cap(s)) s = slices.Grow(s, 10) fmt.Println("After Grow - len:", len(s), "cap:", cap(s)) // Efficient appending for i := 0; i < 10; i++ { s = append(s, fmt.Sprintf("item%d", i)) } fmt.Println("Final slice:", s) }
The empty slice gets capacity for 10 elements. Subsequent appends don't trigger reallocations until we exceed the grown capacity.
Growing with Capacity Already Available
If the slice already has sufficient capacity, slices.Grow
does
nothing. This example demonstrates this behavior.
package main import ( "fmt" "slices" ) func main() { s := make([]int, 0, 10) // Initial capacity 10 fmt.Println("Before Grow - len:", len(s), "cap:", cap(s)) s = slices.Grow(s, 5) // Requesting 5 more fmt.Println("After Grow - len:", len(s), "cap:", cap(s)) // Capacity remains the same fmt.Println("No change if capacity sufficient") }
Since the slice already had capacity for 10 elements, requesting 5 more doesn't change anything. The function returns the original slice.
Growing Beyond Current Capacity
When growing beyond current capacity, the slice gets a new backing array. This example shows the memory allocation behavior.
package main import ( "fmt" "slices" ) func main() { s := []int{1, 2, 3} fmt.Printf("Original array address: %p\n", &s[0]) s = slices.Grow(s, 100) // Large growth fmt.Printf("New array address: %p\n", &s[0]) fmt.Println("Capacity increased from 3 to:", cap(s)) }
The memory address changes because Go allocates a new array. The capacity grows to accommodate both existing elements and the requested growth.
Performance Comparison
This example compares growing vs. repeated appends. It shows the performance benefit of pre-allocation.
package main import ( "fmt" "slices" "time" ) func main() { const size = 1_000_000 // Without pre-allocation start := time.Now() var s1 []int for i := 0; i < size; i++ { s1 = append(s1, i) } fmt.Println("Append without Grow:", time.Since(start)) // With pre-allocation start = time.Now() s2 := make([]int, 0) s2 = slices.Grow(s2, size) for i := 0; i < size; i++ { s2 = append(s2, i) } fmt.Println("Append with Grow:", time.Since(start)) }
Pre-allocating with slices.Grow
is faster because it avoids
multiple reallocations. The difference becomes significant with large slices.
Growing Slice of Structs
slices.Grow
works with any slice type. This example demonstrates
growing a slice of custom structs.
package main import ( "fmt" "slices" ) type Point struct { X, Y float64 } func main() { points := []Point{{1, 2}, {3, 4}} fmt.Println("Initial capacity:", cap(points)) points = slices.Grow(points, 100) fmt.Println("Grown capacity:", cap(points)) // Efficiently add many points for i := 0; i < 100; i++ { points = append(points, Point{float64(i), float64(i * 2)}) } fmt.Println("Final length:", len(points)) }
We grow a slice of Point structs to hold 100 additional elements. The same performance benefits apply to custom types as with built-in types.
Practical Example: Batch Processing
This practical example shows slices.Grow
in a batch processing
scenario where we know the approximate result size.
package main import ( "fmt" "math/rand" "slices" "time" ) func processItem(i int) float64 { time.Sleep(time.Microsecond) // Simulate work return rand.Float64() * float64(i) } func main() { const batchSize = 10_000 results := make([]float64, 0) // Pre-allocate for expected results results = slices.Grow(results, batchSize) start := time.Now() for i := 0; i < batchSize; i++ { results = append(results, processItem(i)) } fmt.Printf("Processed %d items in %v\n", len(results), time.Since(start)) fmt.Println("Final capacity:", cap(results)) }
By growing the slice before processing, we avoid reallocations during the batch. This optimization is especially valuable in performance-critical code.
Source
Go experimental slices package documentation
This tutorial covered the slices.Grow
function in Go with practical
examples of efficient slice capacity management in various scenarios.
Author
List all Go tutorials.