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.