A slice is Go's dynamic array: a view onto a fixed array that quietly moves to a bigger one whenever you append past the room it has.
The rule
Growth by doubling
A full array is replaced by a bigger one, so most appends cost nothing extra.
- Append: drop the value in when there is spare room.
- Grow: when there is none, allocate a larger array and copy everything across.
- Reserve: hand make a capacity up front and the copying never happens.
Press the buttons to append values and watch the capacity. When the array fills up, Go allocates a bigger one and copies everything across.
Purpose
Hold a list that grows as you add to it.
Input
Values appended to a []int, one or many at a time.
Output
The slice, with len counting what is in it and cap the room it has.
Constraints
- Growth is by a factor, not a fixed amount, so
capjumps rather than creeping up. - A grown slice points at a new array, so any other slice over the old one stops seeing the changes.
- A slice of a slice keeps the whole original array alive. Copy it out when you only need a few values.
How a slice grows
Underneath there is a fixed-size array. The slice keeps track of how much of it is in use and how much room is spare. Add a value and there is room, it drops straight in. Add one and there is no room, Go quietly makes a bigger array, copies everything across, and carries on.
That copying is why a slice feels like it has no size limit, and why telling Go up front how big it will get saves work.
What a slice keeps track of
| Name | Type | What it is for |
|---|---|---|
| s | []int | The slice you work with. Three words: a pointer, a length, and a capacity. |
| len(s) | int | How many values are in it right now. |
| cap(s) | int | How many it can hold before it needs a bigger array. |
| append | builtin | Adds a value, growing the array when there is no room left. |
| make | builtin | Creates a slice with a length and, optionally, a capacity you choose. |
Walk through it
1 Start with an empty slice
A slice declared this way holds nothing and owns no array yet. It is still safe to append to.
var s []int
fmt.Println(len(s), cap(s)) // 0 02 Add a value with append
Length goes up by one. Capacity jumps to whatever Go decided to allocate.
s = append(s, 1)
fmt.Println(len(s), cap(s)) // 1 43 Keep going while there is room
The next three appends fit in the space already allocated, so capacity does not move.
s = append(s, 2) // len 2, cap 4
s = append(s, 3) // len 3, cap 4
s = append(s, 4) // len 4, cap 4, now full4 Grow when it runs out
The fifth value has nowhere to go, so Go allocates a bigger array, copies the four across, and adds the new one. Capacity doubles.
s = append(s, 5)
fmt.Println(len(s), cap(s)) // 5 85 Always reassign the result
Append may hand back a slice pointing at a brand new array. Throw the return value away and you lose the value you just added.
s = append(s, 6) // right
append(s, 6) // wrong, the result goes nowhere6 Preallocate when you know the size
Giving make a capacity does the allocation once instead of on every growth step.
s := make([]int, 0, 1000)
for i := 0; i < 1000; i++ {
s = append(s, i) // no copying at all
}The whole program
Put it together and you can watch the capacity jump each time the array runs out of room.
package main
import "fmt"
func main() {
var s []int
fmt.Printf("start len=%d cap=%d\n", len(s), cap(s))
for i := 1; i <= 5; i++ {
s = append(s, i)
fmt.Printf("append %d len=%d cap=%d\n", i, len(s), cap(s))
}
}$ go run main.go
start len=0 cap=0
append 1 len=1 cap=4
append 2 len=2 cap=4
append 3 len=3 cap=4
append 4 len=4 cap=4
append 5 len=5 cap=8