← thecodex.expert · The Codex Family of Knowledge
Go

Slices and Arrays

Arrays exist in Go but are rarely used directly — almost all real code reaches for slices, which can grow and shrink.

Go 1.26 len vs cap Last verified:
Where this bites you

You take the first two items off a slice to hand to one part of your program, keeping the rest untouched: b := a[:2]. You then append one more item to b. Later, something in the untouched part of a has silently changed — a value you never wrote to is suddenly different. Nothing panicked, nothing returned an error. Two slices that look unrelated were quietly sharing memory.

Goslicebug.go
a := []int{1, 2, 3}
b := a[:2]              // b shares a's backing array; cap(b) is still 3
b = append(b, 99)       // fits in a's leftover capacity — writes into a[2]
fmt.Println(a)         // [1 2 99] — a's third element just changed

The fix is in the last section on this page, once append's actual behavior — reuse the backing array when there's room, allocate a new one when there isn't — is on the table.

Canonical Definition

An array’s length is part of its type ([5]int and [10]int are different types), which makes arrays inflexible and rarely used directly. A slice is a small struct — a pointer to an underlying array, a length, and a capacity — that provides a flexible, growable view over that array, and is what idiomatic Go code uses for lists in practice. len(s) reports how many elements a slice currently holds; cap(s) reports how many it could hold before its backing array must be reallocated.

Arrays: fixed size, part of the type

An array's length is fixed at compile time and is part of its type. This makes arrays value types — assigning or passing one copies every element — which is exactly why Go code almost always uses slices instead.

Goarrays.go
var scores [3]int          // array of exactly 3 ints, zero-valued
scores[0] = 90

primes := [5]int{2, 3, 5, 7, 11}
fmt.Println(len(primes))   // 5 — fixed forever for this variable

Slices: a view over an array

A slice is created with []T{...} literal syntax, make([]T, len, cap), or by slicing an existing array/slice with a[low:high]. Two slices can share the same underlying array — mutating one through indexing can be visible through the other, since neither owns a private copy.

Goslices.go
nums := []int{10, 20, 30, 40, 50}   // slice literal
sub := nums[1:3]                     // [20, 30] — shares nums' backing array

sub[0] = 999
fmt.Println(nums)   // [10, 999, 30, 40, 50] — nums changed too!

append() and when a slice grows

append adds elements to a slice. If the slice still has spare capacity, append writes into the existing backing array and returns a slice pointing at the same array. If capacity is exhausted, Go allocates a new, larger backing array (typically doubling for smaller slices), copies the old data over, and returns a slice pointing at the new array — which is why append's result must always be reassigned, since the original variable's backing array may no longer be the current one.

Goappend.go
s := make([]int, 0, 2)   // len 0, cap 2
s = append(s, 1)          // fits: len 1, cap 2, same backing array
s = append(s, 2)          // fits: len 2, cap 2, same backing array
s = append(s, 3)          // doesn't fit: Go allocates a new, bigger array

// always reassign: s := append(s, x) NOT append(s, x) alone

Which is exactly the mechanism behind the aliasing bug at the top of this page: a[:2] has length 2 but capacity 3, since it still shares a's full backing array — so appending to it had room to write straight into a[2] instead of allocating somewhere new. The fix is a three-index slice expression, a[low:high:max], which caps the result's capacity at max so append is forced to allocate a fresh array instead of spilling into the rest of a:

Goslicefix.go
a := []int{1, 2, 3}
b := a[:2:2]            // cap(b) == 2 now — no spare room in a's array
b = append(b, 99)       // capacity exhausted — allocates a new backing array
fmt.Println(a)         // [1 2 3] — untouched
fmt.Println(b)         // [1 2 99] — on its own array now

Sources

1
The Go Authors. "Go Slices: usage and internals", go.dev/blog/slices-intro — the canonical explanation of slice growth and backing arrays.