Dev Skills

Arrays and slices

The data structure you use all day, and the shared-memory trap inside it.

Topic 11 of 98 · 5 min to read · 45 min to build
After this topic you can
  • Explain the difference between a slice's length and its capacity.
  • Predict the output when you write through a sub-slice.
  • Fix a shared-memory bug with copy or a full slice expression s[a:b:b].

What is the difference between an array and a slice?

An array has a fixed length that is part of its type; a slice is a resizable view over an array, and it is what you use day to day. In Go an array’s length is part of its type. [3]int and [4]int are different types, and passing an array to a function copies the whole thing. That is almost never what you want, so arrays mostly exist to be the thing a slice points at.

array.go
var arr [3]int = [3]int{1, 2, 3}
// arr can never hold four elements

What is a slice made of?

A slice is a small three-field value: a pointer to a backing array, a length, and a capacity. Length is how many elements you can index. Capacity is how many fit before Go has to allocate a bigger array.

slices.go
s := []int{1, 2, 3}
s = append(s, 4) // note: append RETURNS the new slice
fmt.Println(len(s), cap(s))
s2 := make([]int, 0, 10) // length 0, room for 10 — no reallocation yet
sub := s[1:3] // elements 1 and 2
Watch out: append may allocate a new array and copy, so it returns a slice. You must write s = append(s, x). A bare append(s, x) with the result unused does not compile. t := append(s, x) followed by more work on s does compile, and is the version that bites.

Why does changing one slice change another?

Because slicing shares memory. A sub-slice does not copy. It points into the same backing array. Change one, and you have changed the other.

sharing.go
data := []int{1, 2, 3, 4, 5}
head := data[:2]
head[0] = 99
fmt.Println(data) // [99 2 3 4 5] — data changed too
// When you want independence, copy
cp := make([]int, len(data))
copy(cp, data)
cp[0] = 1 // data is untouched
This surprises most people new to Go. If two pieces of code hold slices of the same array, they share it.

How do you stop append from overwriting the original? (s[a:b:b])

Cap the capacity with a full slice expression, s[a:b:b], so the next append has to copy. A sub-slice usually keeps the spare capacity of the array behind it. So append on the sub-slice can write straight over elements the original still uses. A full slice expression s[low:high:max] sets the capacity to max - low. Write s[a:b:b] and capacity equals length, so the next append has no room and copies to a new array instead of overwriting.

fullslice.go
data := []int{1, 2, 3, 4, 5}
a := data[0:2] // len 2, cap 5 — spare room is data's elements
a = append(a, 99)
fmt.Println(data) // [1 2 99 4 5] — append overwrote data[2]!
data = []int{1, 2, 3, 4, 5}
b := data[0:2:2] // len 2, cap 2 — no spare room
b = append(b, 99) // must copy to a new array
fmt.Println(data) // [1 2 3 4 5] — untouched
fmt.Println(b) // [1 2 99]
Use `s[a:b:b]` when you hand a piece of your slice to other code and do not want its appends to touch your data.

Try it yourself

press Run — this executes on the Go Playground
package main
import "fmt"
func main() {
data := []int{1, 2, 3, 4, 5}
head := data[:2]
head[0] = 99
fmt.Println("data after writing to the sub-slice:", data) // 99 is there too
cp := make([]int, len(data))
copy(cp, data)
cp[0] = 1
fmt.Println("after copy, data is untouched:", data)
safe := data[0:2:2]
safe = append(safe, 7)
fmt.Println("capped append left data alone:", data, safe)
}
The shared backing array, `copy` for independence, and `s[a:b:b]` for a safe append.
The short version
  • An array’s length is part of its type. You will rarely use one directly.
  • A slice is a pointer, a length and a capacity. append returns a new slice, so always reassign.
  • Sub-slices share the backing array, so a write through one is visible through the other.
  • copy when you need independence. s[a:b:b] caps the capacity so the next append reallocates.
Remember: A slice is a window onto an array, not the array itself. Two windows see each other’s writes.
Build this before moving on
Take a sub-slice, write to it, and print the original. Then fix it with copy and prove the original is untouched.
Coding challenge · checked by tests
Write Reversed(s []int) []int. It returns a NEW slice with the elements in reverse order and must not change s. Example: Reversed([]int{1, 2, 3}) → [3 2 1], and the input is still [1 2 3].

Tab indents. Press Esc, then Tab, to leave the editor.

Then try these in your own editor
  1. 1Append to a slice inside a function without returning it, and watch the caller see nothing.
  2. 2Call append on a nil slice and confirm it works.
  3. 3Preallocate with make([]T, 0, n) and print len and cap as you append.
Check yourselfTake the Core language test