Skip to content
AgileLogics
7 min read

Slices

Prerequisites: Arrays

A slice is a flexible, growable view of a sequence of values — the list type you'll use most in Go. Its type is written without a length: []int, []string.

Under the hood, a slice is a small descriptor with three parts: a pointer to an underlying (backing) array, a length (how many elements it currently holds), and a capacity (how many fit before a bigger backing array is needed).

Syntax

nums := []int{1, 2, 3}      // slice literal
buf := make([]int, 3, 10)   // length 3, capacity 10
var empty []int             // nil slice: length 0, ready for append

nums = append(nums, 4, 5)   // append returns the (possibly new) slice
len(nums)                   // number of elements
cap(nums)                   // capacity

nums[1:3]                   // elements at index 1 and 2
nums[:2]                    // from the start up to (not including) 2
nums[2:]                    // from index 2 to the end

Example

package main

import "fmt"

func main() {
    nums := []int{1, 2, 3}
    nums = append(nums, 4, 5)
    fmt.Println(nums, len(nums))

    buf := make([]int, 3, 10)
    fmt.Println(buf, len(buf), cap(buf))

    var names []string
    fmt.Println(names == nil, len(names))
    names = append(names, "Ada", "Alan")
    fmt.Println(names)

    part := nums[1:3]
    fmt.Println(part, len(part))

    part[0] = 99 // changes the shared backing array
    fmt.Println(nums)

    independent := make([]int, len(part))
    copy(independent, part)
    independent[0] = 0
    fmt.Println(part, independent)
}

Expected output

[1 2 3 4 5] 5
[0 0 0] 3 10
true 0
[Ada Alan]
[2 3] 2
[1 99 3 4 5]
[99 3] [0 3]

The important line is [1 99 3 4 5]: slicing does not copy. part and nums share the same backing array, so changing part[0] changed nums[1] too. When you need an independent copy, use make plus the built-in copy, as shown.

When append runs out of capacity, it allocates a new, larger backing array and copies the elements over — that's why you must always use the slice append returns. How much the capacity grows is an implementation detail, so don't rely on exact cap values after appending.

The Go 1.21+ slices package adds helpers such as slices.Contains, slices.Index, and slices.Sort.

Common mistakes

  • Calling append without keeping the result. append(nums, 4) on its own is a compile error (append(nums, 4) (value of type []int) is not used); write nums = append(nums, 4).
  • Forgetting that sub-slices share memory with the original, then being surprised when a change "leaks" between them.
  • Indexing past len — nums[10] on a 5-element slice panics with index out of range [10] with length 5, even if the capacity is larger.
  • Confusing make([]int, 5) (five zeros already inside) with make([]int, 0, 5) (empty, room for five). Appending to the first gives six elements.

Practice

Starting from an empty slice, use a loop and append to build a slice of the squares of 1 to 5. Print the whole slice, its length, and then only the last three squares using a slicing expression.

Show solution
package main

import "fmt"

func main() {
    var squares []int
    for i := 1; i <= 5; i++ {
        squares = append(squares, i*i)
    }

    fmt.Println(squares)      // [1 4 9 16 25]
    fmt.Println(len(squares)) // 5
    fmt.Println(squares[2:])  // [9 16 25]
}