Skip to content

Slices

An array in Go has a fixed size that is part of its type. [3]int and [4]int are two different types:

arr := [3]int{10, 20, 30}
fmt.Println(len(arr)) // 3 — always

Arrays are value types: assigning a2 := a1 copies every element. They are useful for fixed-size data but too rigid for most work.

A slice is a lightweight header — three words — that describes a window into an underlying array:

flowchart LR
  subgraph Header["slice header"]
    P["pointer"]
    L["len = 3"]
    C["cap = 5"]
  end
  subgraph Backing["backing array"]
    A0["10"] --> A1["20"] --> A2["30"] --> A3["40"] --> A4["50"]
  end
  P --> A0
A slice header points into a backing array; len=3 is visible, cap=5 reaches the end
  • len — number of elements currently accessible through the slice.
  • cap — number of elements from the slice’s start to the end of the backing array.

Create a slice with make, a composite literal, or by slicing an array:

s := make([]int, 3, 5) // len=3, cap=5, all zeros
t := []int{1, 2, 3} // len=3, cap=3
u := arr[1:3] // len=2, cap=2, window into arr

append adds elements to a slice and returns the (possibly new) slice:

s = append(s, 4, 5, 6)

When len == cap, append allocates a new, larger backing array, copies existing elements, and returns a slice pointing to the new array. The old slice is unaffected. Always reassign the result of appends = append(s, ...).

Slicing expressions and shared backing arrays

Section titled “Slicing expressions and shared backing arrays”

a[low:high] creates a new slice header that shares the same backing array:

base := []int{1, 2, 3, 4, 5}
a := base[1:3] // [2 3] — shares backing array with base
b := base[1:4] // [2 3 4] — also shares
a[0] = 99
fmt.Println(b[0]) // 99 — mutation visible through b too

This is powerful (zero-copy sub-slicing) but dangerous if you forget about it.

Use the built-in copy(dst, src) to break the sharing. It copies min(len(dst), len(src)) elements and returns the number copied:

fresh := []int{10, 20, 30}
dst := make([]int, 3)
copy(dst, fresh)
dst[0] = 0
fmt.Println(fresh[0], dst[0]) // 10 0 — independent
package main
import "fmt"
func main() {
// Array vs slice
arr := [3]int{10, 20, 30} // fixed-size array
sl := arr[:] // slice backed by arr
sl[0] = 99
fmt.Println(arr[0], sl[0]) // 99 99 — shared backing array
// make: len and cap
s := make([]int, 3, 5)
fmt.Println(len(s), cap(s)) // 3 5
// append beyond cap allocates a new backing array
s = append(s, 1, 2, 3)
fmt.Println(len(s), cap(s)) // 6 10
// Two slices sharing the same backing array
base := []int{1, 2, 3, 4, 5}
a := base[1:3] // [2 3]
b := base[1:4] // [2 3 4]
a[0] = 99
fmt.Println(b[0]) // 99 — shared backing array
// copy to get an independent slice
fresh := []int{10, 20, 30}
dst := make([]int, 3)
copy(dst, fresh)
dst[0] = 0
fmt.Println(fresh[0], dst[0]) // 10 0 — independent after copy
}
You write `s = append(s, 1)` when `len(s) == cap(s)`. What does Go do?
You have `base := []int{1, 2, 3}` and `a := base[0:2]`. You then set `a[0] = 99`. What is `base[0]`?
What does `make([]string, 4, 10)` create?
What is the idiomatic way to ensure two slices do NOT share a backing array?