Slices in Depth
Prerequisites: Closures & function values
A slice is a small descriptor with three parts: a pointer to a backing array, a length, and a capacity. Slicing an existing slice creates a new descriptor that points into the same array. Knowing this explains most slice surprises.
- When
appendhas spare capacity it writes into the existing array — which other slices may share. - When capacity runs out,
appendallocates a larger array and copies the elements. The exact growth factor is an implementation detail, so never rely on it; if you know the final size, preallocate withmake([]T, 0, n). copy(dst, src)copiesmin(len(dst), len(src))elements into a separate array and returns the count.
The standard library's slices and maps packages (Go 1.21+) provide
generic helpers so you rarely write these loops by hand.
Syntax
s[low:high] // shares the backing array
s[low:high:max] // full slice expression: capacity is max-low
n := copy(dst, src)
slices.Sort(s)
slices.Contains(s, v)
slices.Index(s, v)
slices.Clone(s)
slices.Equal(a, b)
keys := slices.Sorted(maps.Keys(m)) // Go 1.23: maps.Keys returns an iterator
Example
package main
import (
"fmt"
"maps"
"slices"
)
func main() {
// Slicing shares the backing array.
base := []int{1, 2, 3, 4, 5}
part := base[1:3]
part[0] = 99
fmt.Println(base, part, len(part), cap(part))
// append writes into the shared array while capacity remains.
part = append(part, 100)
fmt.Println(base)
// copy makes an independent slice.
clone := make([]int, len(base))
n := copy(clone, base)
clone[0] = -1
fmt.Println(n, base[0], clone[0])
// A full slice expression caps capacity, forcing append to reallocate.
safe := base[1:3:3]
safe = append(safe, 7)
fmt.Println(base, safe)
// The slices package.
words := []string{"pear", "apple", "fig"}
slices.Sort(words)
fmt.Println(words)
fmt.Println(slices.Contains(words, "fig"), slices.Index(words, "pear"))
fmt.Println(slices.Max([]int{4, 9, 2}))
// Map iteration order is random; sort the keys for stable output.
ages := map[string]int{"bob": 31, "ada": 36, "cy": 25}
for _, k := range slices.Sorted(maps.Keys(ages)) {
fmt.Println(k, ages[k])
}
}
Expected output
[1 99 3 4 5] [99 3] 2 4
[1 99 3 100 5]
5 1 -1
[1 99 3 100 5] [99 3 7]
[apple fig pear]
true 2
9
ada 36
bob 31
cy 25
Notice the second line: appending to part silently overwrote base[3].
Common mistakes
- Discarding the result of
append(append(s, x)on its own line). The result may be a different slice; always writes = append(s, x). - Passing a slice to a function that appends to it and expecting the caller's slice to grow. The function receives a copy of the descriptor; return the new slice instead.
- Holding a tiny sub-slice of a huge slice, which keeps the whole backing
array in memory.
slices.Clonethe part you need. - Comparing slices with
==— it only compiles againstnil. Useslices.Equal.
Practice
Given nums := []int{5, 3, 8, 3, 1}, produce a sorted copy with duplicates
removed without modifying nums. Print both slices. (Hint:
slices.Clone, slices.Sort, slices.Compact.)
Show solution
package main
import (
"fmt"
"slices"
)
func main() {
nums := []int{5, 3, 8, 3, 1}
unique := slices.Clone(nums)
slices.Sort(unique)
unique = slices.Compact(unique) // removes consecutive duplicates
fmt.Println(unique) // [1 3 5 8]
fmt.Println(nums) // [5 3 8 3 1]
}