Skip to content
AgileLogics
8 min read

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 append has spare capacity it writes into the existing array — which other slices may share.
  • When capacity runs out, append allocates 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 with make([]T, 0, n).
  • copy(dst, src) copies min(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 write s = 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.Clone the part you need.
  • Comparing slices with == — it only compiles against nil. Use slices.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]
}