Arrays and slices in Go

Understand arrays and slices in Go: slice literals, len and cap, append, slicing, make, ranging, and how slices share a backing array.

Prerequisites

Learning objectives

  • Tell arrays and slices apart and know when to use each
  • Grow slices with append and read len and cap
  • Understand that slices share a backing array

An array has a fixed length that is part of its type. A slice is a flexible, growable view over an array. In everyday Go you reach for slices almost always, but understanding arrays underneath is what makes slices click.

Arrays: a fixed size

An array’s length is fixed at declaration and cannot change. The size is part of the type, so [3]string and [4]string are different types.

var days [3]string
days[0] = "Mon"
days[1] = "Tue"
days[2] = "Wed"

Because the length is baked in, arrays are rigid. That is why slices exist.

Slices: a flexible view

A slice literal looks like an array literal without the size. A slice has no fixed length and can grow.

primes := []int{2, 3, 5, 7}

len and cap

len returns how many elements a slice currently holds. cap returns how many it can hold before it must grow its underlying storage.

s := []int{2, 3, 5, 7}
fmt.Println(len(s), cap(s)) // 4 4

Growing a slice with append

append returns a new slice with the extra elements. Always assign the result back — usually to the same variable.

s := []int{1, 2}
s = append(s, 3, 4)
// s is now [1 2 3 4]

Slicing with a[low:high]

a[low:high] produces a slice from index low up to, but not including, high. Either bound can be omitted to mean “the start” or “the end”.

letters := []string{"a", "b", "c", "d", "e"}
fmt.Println(letters[1:3]) // [b c]
fmt.Println(letters[:2])  // [a b]
fmt.Println(letters[3:])  // [d e]

Iterating with range

for range walks a slice, giving the index and a copy of each element.

for i, v := range primes {
	fmt.Println(i, v)
}

Creating slices with make

make builds a slice with a given length and, optionally, a capacity. It is the right tool when you know roughly how many elements you will add.

scores := make([]int, 0, 4) // len 0, cap 4
scores = append(scores, 10) // no reallocation yet

Slices share a backing array

This is the classic gotcha. A slice points at an array, and sub-slices share that same array. Writing through one view changes the other.

base := []int{1, 2, 3, 4}
view := base[1:3] // [2 3]
view[0] = 99
fmt.Println(base) // [1 99 3 4]

A complete example

This program builds a slice, appends to it, takes a sub-slice, and sums values gathered with make. It compiles and runs as-is.

package main

import "fmt"

func main() {
	// A slice literal.
	primes := []int{2, 3, 5, 7}
	fmt.Println("primes:", primes, "len:", len(primes), "cap:", cap(primes))

	// Grow it with append.
	primes = append(primes, 11, 13)
	fmt.Println("after append:", primes)

	// A sub-slice shares the same backing array.
	firstThree := primes[:3]
	fmt.Println("first three:", firstThree)

	// make creates a slice with a length and capacity.
	scores := make([]int, 0, 4)
	for i := 1; i <= 4; i++ {
		scores = append(scores, i*i)
	}

	// Iterate with range.
	sum := 0
	for _, s := range scores {
		sum += s
	}
	fmt.Println("scores:", scores, "sum:", sum)
}

Running it prints:

primes: [2 3 5 7] len: 4 cap: 4
after append: [2 3 5 7 11 13]
first three: [2 3 5]
scores: [1 4 9 16] sum: 30

Common mistakes

  • Discarding the result of append. append(s, x) alone loses the new slice; you must write s = append(s, x).
  • Forgetting the shared backing array. Two slices from the same array see each other’s writes. Copy when you need independence.
  • Confusing len and cap. len is what you can read today; cap is how far the storage reaches before a reallocation.

Practice

Given numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, build a new slice that holds only the even numbers and print it.

Show the solution
package main

import "fmt"

func main() {
	numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

	evens := make([]int, 0, len(numbers))
	for _, n := range numbers {
		if n%2 == 0 {
			evens = append(evens, n)
		}
	}

	fmt.Println(evens)
}

Starting from an empty slice with capacity len(numbers) avoids reallocations, and the range loop appends each even value, printing [2 4 6 8 10].

Summary

  • Arrays have a fixed length; slices are flexible views you use most of the time.
  • append returns a new slice, len/cap describe it, and make pre-sizes it.
  • Slices share a backing array, so copy when you need an independent one.

Next, learn about Go’s other core collection, the key-value maps, and revisit iteration in loops.

Your progress is stored only in this browser. No account is required.

Search

Search runs entirely in your browser.