Slices
Dynamic sequences built on arrays - make, literals, append, copy, sub-slicing, and the len vs cap distinction.
A slice is a view into an underlying array. It has three fields: a pointer to the array, a length, and a capacity. Slices are the workhorse collection in Go - dynamic, passed by reference semantics, and composable.
Create a slice with make([]T, length, capacity) or with a slice literal. Both are nil-free and immediately usable. len returns the number of elements; cap returns the underlying array's size from the slice's start.
package main
import "fmt"
func main() {
// Slice literal
s := []int{1, 2, 3}
fmt.Println(s, len(s), cap(s)) // [1 2 3] 3 3
// make: length 3, capacity 5
t := make([]int, 3, 5)
fmt.Println(t, len(t), cap(t)) // [0 0 0] 3 5
}append adds elements to a slice. If the slice has spare capacity, it appends in-place and returns a slice with a larger length. If capacity is exhausted, it allocates a new backing array, copies existing elements, and returns a slice pointing to the new array.
package main
import "fmt"
func main() {
s := make([]int, 0, 3)
for i := 0; i < 5; i++ {
s = append(s, i)
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
// len=1 cap=3 [0]
// len=2 cap=3 [0 1]
// len=3 cap=3 [0 1 2]
// len=4 cap=6 [0 1 2 3] ← new backing array allocated
// len=5 cap=6 [0 1 2 3 4]
}A sub-slice s[low:high] shares the same backing array as the original. Writes through the sub-slice mutate the original. Use copy to get an independent slice.
package main
import "fmt"
func main() {
orig := []int{1, 2, 3, 4, 5}
// Sub-slice shares backing array
sub := orig[1:4] // [2 3 4]
sub[0] = 99
fmt.Println(orig) // [1 99 3 4 5] - original mutated!
// Independent copy
dst := make([]int, len(orig))
copy(dst, orig)
dst[0] = 0
fmt.Println(orig) // [1 99 3 4 5] - original untouched
fmt.Println(dst) // [0 99 3 4 5]
}In production
Two slices sharing a backing array is the most common source of subtle mutation bugs in Go. A sub-slice write reaches through to the parent. In high-throughput services, pre-allocate with make([]T, 0, n) when you know the approximate size - it avoids repeated allocations and the GC pressure that comes with them. append doubling capacity on each overflow sounds cheap, but 10 000 appends to an unallocated slice trigger ~14 allocations and copies; a single make with a sensible capacity eliminates all of them.
Enjoyed this? Get more essays on software craft delivered to your inbox.
Subscribe free