Slices
Slices are the workhorse of Go. They're flexible, growable views onto an underlying array, and you'll reach for them constantly. You'll learn to create, append to, and re-slice them — and understand the one thing that surprises everyone: slices share their backing storage, so a copy can change the original.
Learn Slices in our free Go course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick recall.
Part of the free Go course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
1️⃣ Creating & Growing
A slice type has no length: []int . Make one from a literal, from make([]T, len) , or start with a nil slice and grow it. The built-in append is how you add elements — and it returns a new slice , so you must always write s = append(s, x) , never just append(s, x) .
2️⃣ Slicing, len & cap
The slice expression s[low:high] gives you elements from low up to (but not including) high . Omit either bound to mean "start" or "end". len is how many elements you have; cap is how many the backing array can hold before append must allocate a bigger one. Watch the last line carefully — appending within capacity writes through to the shared backing array.
3️⃣ References & copy()
Unlike arrays, a slice is a small header (pointer, length, capacity) that points at a backing array. Copying the header — by assignment or by passing to a function — shares that backing array, so mutations are visible everywhere. When you need a genuinely independent copy, use the built-in copy(dst, src) into a slice you make yourself.
Your turn — build a queue with append , then count it.
Now grab the first three and last two with slice expressions.
These lines build a slice with append , but they're scrambled. Put them in order so the program prints [1 2 3] (assume a func main wraps them).
Why: the slice must be declared first (a nil slice is fine). Each append reassigns s with the returned slice — order matters because appending 1 then 2,3 gives [1 2 3] . The print comes last, after both appends.
Predict the output before you reveal the answer.
[2 3] — s[1:] starts at index 1 and runs to the end, dropping the first element.
{'a := []int ; b := a; b[0] = 9; fmt.Println(a[0])'}
9 — b := a copies the slice header, but both share the same backing array, so writing b[0] changes a[0] too.
0 true — an un-initialised slice is nil with length 0, yet it's still safe to append to.
Q: What's the difference between len and cap?
len is how many elements the slice currently holds; cap is how many the backing array can hold before append must allocate a larger one and copy everything over.
Q: Why did changing my slice copy also change the original?
Both slices share the same backing array. Assignment copies the header, not the data. Use copy() into a fresh make 'd slice for true independence.
Q: Do I need make if I'm going to append anyway?
Not strictly — appending to a nil slice works. But make([]T, 0, n) pre-allocates capacity, which avoids repeated reallocations when you know the rough size.
Loop the numbers, test each with n%2 == 0 , and append the ones that pass into a fresh slice. Write it yourself and match the example output.
Practice quiz
What is the type of a slice of ints?
A slice type has no length in it: []int, unlike an array [5]int.
What does append return, and how must you use it?
- A new slice you must reassign: s = append(s, x)
- It mutates in place; ignore the return
- An error value
- The appended element
Answer: A new slice you must reassign: s = append(s, x). append returns a (possibly new) slice, so always write s = append(s, x).
What is the value and nil-status of an uninitialised var s []int?
- empty, not nil
- length 1 and nil
- it is a compile error
- length 0 and s == nil is true
Answer: length 0 and s == nil is true. A zero-value slice is nil with length 0, yet it is still safe to append to.
What does s[1:4] evaluate to for s := []int{0,1,2,3,4,5}?
s[low:high] takes indices low..high-1, so 1,2,3 -> [1 2 3].
What is the difference between len and cap?
- len is current elements; cap is room before append must reallocate
- They are always equal
- cap is elements; len is room
- len is bytes; cap is elements
Answer: len is current elements; cap is room before append must reallocate. len is how many elements you have; cap is how many the backing array can hold.
After b := a (both []int) and b[0] = 99, what happens to a[0]?
- a is unchanged
- It panics
Assignment copies the slice header; both share the same backing array.
How do you get a genuinely independent copy of a slice?
- b := a
- copy(dst, src) into a make'd slice
- append(nil, a)
Answer: copy(dst, src) into a make'd slice. copy(dst, src) into a freshly make'd slice gives independent storage.
What does copy return?
- The destination slice
- An error
- Nothing
- The number of elements copied
Answer: The number of elements copied. copy returns the number of elements copied: min(len(dst), len(src)).
Which is the #1 slice bug?
- Using make
- Writing append(s, x) without reassigning to s
Answer: Writing append(s, x) without reassigning to s. Dropping the assignment loses the appended element — always s = append(s, x).
How do you pre-size a slice to avoid repeated regrows when you know the count?
make([]T, 0, n) sets length 0 and capacity n so appends don't reallocate early.