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.
🧩 Reorder Challenge
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).
- s = append(s, 1)
- s = append(s, 2, 3)
- fmt.Println(s)
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.
Pro Tips
- 💡 Always reassign: s = append(s, x) . Dropping the assignment is the #1 slice bug.
- 💡 Pre-size with make([]T, 0, n) when you know roughly how many elements are coming — it avoids repeated regrows.
- 💡 Use copy() for independence. If you need a slice that won't move with the original, copy into a fresh one.
- 💡 Beware sub-slices keeping big arrays alive. A tiny sub-slice of a huge slice still references the whole backing array; copy it out to free the rest.
Common Errors (and the fix)
- "append result discarded"-style bugs — you wrote append(s, x) without s = . The new element vanishes. Reassign the result.
- Mutating a copy changes the original — that's expected: slices share backing storage. Use copy() for an independent slice.
- "slice bounds out of range [:6] with capacity 5" — you re-sliced past the capacity. Check cap(s) first, or use append.
- "index out of range" on a nil or empty slice — you indexed before appending. A nil slice has length 0; append before you index.
📋 Quick Reference
Task
Syntax
Literal
s := []int{1, 2, 3}
make
make([]int, 0, 10)
Append
s = append(s, x)
Sub-slice
s[1:4]
Length / cap
len(s), cap(s)
Copy
copy(dst, src)
🧠 Quick Recall
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{1,2}; 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.
Frequently Asked Questions
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.
Mini-Challenge: Filter Evens
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.
🎉 Lesson Complete!
- ✅ Slices ( []T ) are flexible, growable views — no length in the type
- ✅ append returns a new slice; always write s = append(s, x)
- ✅ Re-slice with s[low:high] ; len is size, cap is room to grow
- ✅ Slices are references sharing a backing array — copies can change the original
- ✅ Use copy(dst, src) for a genuinely independent slice
- ✅ Next lesson: Maps — key/value lookup tables
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.