HowtoGo
Home / Data Structures / Dynamic Array
Data Structures

Dynamic Array

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.
A slice filling its backing array and moving to a larger one when it runs out of room
in use (len) spare (cap)

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 cap jumps 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

NameTypeWhat it is for
s[]intThe slice you work with. Three words: a pointer, a length, and a capacity.
len(s)intHow many values are in it right now.
cap(s)intHow many it can hold before it needs a bigger array.
appendbuiltinAdds a value, growing the array when there is no room left.
makebuiltinCreates 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 0

2 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 4

3 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 full

4 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 8

5 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 nowhere

6 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))
    }
}
Terminal
$ 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