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.

Double the room when it runs out

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.

Length, capacity, and the array under it

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