Arrays & Slices

Go · Lesson 14 of 32 6 min read Updated

Most programs work with lists: the marks of a class, the items in a cart, the runs in each over. Go has two list types. An array has a fixed length that never changes. A slice is a flexible view onto an array that can grow, and it is what you will use almost all the time.

In this lesson you will meet arrays briefly, then learn slices properly: creating them, growing them with append, cutting them with [low:high], the surprise of shared memory, and the slices package that sorts and searches for you.

Arrays: fixed length

An array type includes its length: [7]int is "seven ints", and [7]int and [8]int are different types. Arrays start filled with zero values. Assigning an array to another variable copies all of it.

Go
package main

import "fmt"

func main() {
	var sales [7]int // one number per day of the week
	sales[0] = 1200
	sales[6] = 2100
	fmt.Println(sales, len(sales))

	primes := [...]int{2, 3, 5, 7} // [...] counts the values for you
	backup := primes
	backup[0] = 100
	fmt.Println(primes, backup)
}
Output
[1200 0 0 0 0 0 2100] 7
[2 3 5 7] [100 3 5 7]

Changing backup did not touch primes, because the assignment made a full copy. Arrays are useful when the size is truly fixed, such as the 7 days of a week or the 3 parts of an RGB colour. For everything else, use slices.

Slices: flexible lists

A slice type has no length in the brackets: []string. A slice has a length (len, how many elements it holds now) and a capacity (cap, how many fit in the array behind it before Go must allocate a bigger one). The zero value of a slice is nil, which behaves like an empty list.

append adds elements and returns the updated slice, so you must assign the result back:

Go
package main

import "fmt"

func main() {
	var cart []string
	fmt.Println(cart == nil, len(cart), cap(cart))

	cart = append(cart, "rice")
	cart = append(cart, "dal", "ghee")
	fmt.Println(cart, len(cart))

	cart[0] = "basmati rice"
	fmt.Println(cart[0], "|", cart[len(cart)-1])
}
Output
true 0 0
[rice dal ghee] 3
basmati rice | ghee

Indexes start at 0, so the last element is at len(cart)-1. Reading cart[5] on a slice of length 3 stops the program with panic: runtime error: index out of range [5] with length 3.

make: slices with a planned size

make([]T, length, capacity) creates a slice with a given length (filled with zero values) and an optional capacity. If you know roughly how many items you will add, give that capacity up front so append never needs to reallocate:

Go
package main

import "fmt"

func main() {
	marks := make([]int, 3)
	fmt.Println(marks, len(marks), cap(marks))

	ids := make([]int, 0, 10)
	for i := range 4 {
		ids = append(ids, 101+i)
	}
	fmt.Println(ids, len(ids), cap(ids))
}
Output
[0 0 0] 3 3
[101 102 103 104] 4 10

The second slice started empty with room for 10. After four appends its length is 4 and its capacity is still 10. When a slice runs out of capacity, append copies everything into a bigger array; the exact growth size is an implementation detail, so never rely on a particular capacity after appending.

Slicing: views that share memory

The expression s[low:high] makes a new slice from index low up to, but not including, high. Leave out low to start at 0, or high to go to the end. The important part: the new slice shares the same array as the original. Changing one changes the other.

Go
package main

import "fmt"

func main() {
	runs := []int{10, 25, 4, 61, 0, 33}

	firstThree := runs[:3]
	middle := runs[2:5]
	fmt.Println(firstThree, middle, runs[4:])

	middle[0] = 99
	fmt.Println(runs)
	fmt.Println(firstThree)
}
Output
[10 25 4] [4 61 0] [0 33]
[10 25 99 61 0 33]
[10 25 99]

middle[0] and runs[2] and firstThree[2] are the same place in memory, so one assignment changed all three views. Slicing is cheap because nothing is copied, but you must remember the sharing.

Advanced: the append surprise

Sharing can also bite through append. If a slice has spare capacity, two appends to it write into the same spare slot:

Go
package main

import (
	"fmt"
	"slices"
)

func main() {
	base := make([]int, 3, 5) // length 3, room for 5
	a := append(base, 1)
	b := append(base, 2)
	fmt.Println(a, b)

	c := append(slices.Clone(base), 1)
	d := append(slices.Clone(base), 2)
	fmt.Println(c, d)
}
Output
[0 0 0 2] [0 0 0 2]
[0 0 0 1] [0 0 0 2]

Both a and b used index 3 of the same array, so the second append overwrote the first. slices.Clone makes an independent copy, which fixes it. The built-in copy(dst, src) also copies elements into an existing slice and returns how many it copied.

The slices package

The standard slices package (Go 1.21) has ready-made functions for common jobs, and they work for slices of any element type:

Go
package main

import (
	"fmt"
	"slices"
)

func main() {
	scores := []int{72, 45, 91, 60}

	sorted := slices.Clone(scores)
	slices.Sort(sorted)
	fmt.Println(scores, sorted)

	fmt.Println(slices.Contains(scores, 91), slices.Index(scores, 60))
	fmt.Println(slices.Max(scores), slices.Min(scores))

	names := []string{"Meera", "aarav", "Kabir"}
	slices.Sort(names)
	fmt.Println(names)

	slices.Reverse(sorted)
	fmt.Println(sorted)
}
Output
[72 45 91 60] [45 60 72 91]
true 3
91 45
[Kabir Meera aarav]
[91 72 60 45]

slices.Sort sorts in place, which is why we cloned first to keep the original order. Strings sort by their bytes, so every capital letter comes before every small letter: "aarav" lands last. Other useful functions are slices.Equal, slices.Insert, slices.Delete and slices.SortFunc for custom orders.

Common mistakes

  • Not assigning append's result. append(cart, "salt") alone does nothing useful; write cart = append(cart, "salt").
  • Forgetting that sub-slices share memory. Clone first if you need an independent copy.
  • Index out of range. Check len(s) before reading s[i].
  • Comparing slices with ==. Slices can only be compared with nil. Use slices.Equal(a, b).

Key points

  • Arrays have a fixed length that is part of their type and are copied on assignment.
  • Slices have a length and a capacity; their zero value is nil, which acts as empty.
  • append returns the updated slice; make([]T, len, cap) pre-sizes a slice.
  • s[low:high] creates a view that shares the same underlying array.
  • Use slices.Clone for independent copies and the slices package to sort and search.

Exercise

A Jaipur bakery records the number of cakes sold each day: 14, 9, 22, 17, 5, 30, 12. Store them in a slice. Print the best and worst day's sales, the total, and the sales of the last three days using slicing. Then print a sorted copy while keeping the original order intact.

Show solution
Go
package main

import (
	"fmt"
	"slices"
)

func main() {
	cakes := []int{14, 9, 22, 17, 5, 30, 12}

	total := 0
	for _, c := range cakes {
		total += c
	}
	fmt.Println("Best:", slices.Max(cakes), "Worst:", slices.Min(cakes), "Total:", total)
	fmt.Println("Last three days:", cakes[len(cakes)-3:])

	sorted := slices.Clone(cakes)
	slices.Sort(sorted)
	fmt.Println("Sorted:", sorted)
	fmt.Println("Original:", cakes)
}
Output
Best: 30 Worst: 5 Total: 109
Last three days: [5 30 12]
Sorted: [5 9 12 14 17 22 30]
Original: [14 9 22 17 5 30 12]

cakes[len(cakes)-3:] starts three from the end and runs to the end. Cloning before sorting keeps the daily order.

Quick Quiz

1. What is printed by s := []int{1, 2, 3}; t := s[:2]; t[0] = 9; fmt.Println(s)?

2. Which line correctly adds an item to a slice?

3. What is the length and capacity of make([]int, 2, 8)?

4. How do you compare two slices for equal contents?

Learn Go with a mentor

Free lessons get you started. Our Programming & Software Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call