Slice Internals

A slice is a three-word header (pointer, length, capacity) over a backing array, and understanding how append grows, when slices share storage, and how copy breaks that sharing is what separates working Go from buggy Go.

Learn Slice Internals in our free Go course — an interactive lesson with runnable examples, a practice exercise and a quick reference.

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️⃣ The Header & How append Grows

Every slice is a three-word value: a pointer to the backing array, a length , and a capacity . While len < cap , append writes in place; when the array is full, it allocates a bigger one , copies everything over, and returns a slice pointing at the new array. Watch the capacity roughly double (1, 2, 4, 8) — that amortizes the cost of growth, but you shouldn't depend on the exact numbers.

2️⃣ Shared Backing Arrays & Aliasing Bugs

A sub-slice like base[1:3] shares base 's backing array — it even inherits its remaining capacity. So writing through the sub-slice mutates base , and an append that stays within capacity will overwrite later elements of base . This is the classic aliasing bug. The cure is copy(dst, src) into a slice you make yourself, which gives a genuinely independent slice.

3️⃣ Full-Slice Expression & the slices Package

The three-index full-slice expression s[low:high:max] caps the capacity at max - low . By making the sub-slice "full," the very next append is forced to reallocate instead of clobbering the parent — a safe way to hand out a view. Since Go 1.21 the slices package adds ergonomic helpers: Sort , Contains , Index , and Equal .

🎯 Your Turn

Make an independent copy so changing dst never touches src . Fill in the two blanks.

Implement removeAt using the order-not-preserved trick: copy the last element over index i , then return s[:len(s)-1] . It's O(1) because it avoids shifting.

Practice quiz

What three words make up a slice header?

  • length, capacity, type
  • pointer, type, length
  • pointer, length, capacity
  • start, end, step

Answer: pointer, length, capacity. A slice is a 3-word header: pointer to the backing array, length, and capacity.

Starting from a nil slice and appending 0,1,2,3,4,5, what capacity does len 3 reach?

  • 4
  • 3
  • 6
  • 8

Answer: 4. Growth roughly doubles: cap goes 1, 2, 4 — so at len 3 the cap is 4.

When does append grow the backing array?

  • On every call
  • Only for slices of structs
  • Never — it always reuses
  • Only when len equals cap (no room left)

Answer: Only when len equals cap (no room left). If len < cap append writes in place; when full it allocates a bigger array and copies.

For base := []int{1,2,3,4,5}, what is cap(base[1:3])?

  • 2
  • 4
  • 3
  • 5

Answer: 4. A sub-slice inherits the parent's remaining capacity: from index 1 to the end is 4.

Why can appending to a sub-slice silently corrupt the parent slice?

  • They share a backing array, so an in-capacity append overwrites the parent's later elements
  • append always copies
  • Sub-slices are read-only
  • cap is always 0

Answer: They share a backing array, so an in-capacity append overwrites the parent's later elements. A sub-slice shares storage; an append within capacity writes into the parent's array.

What does the full-slice expression s[low:high:max] control?

  • The length only
  • The element type
  • The capacity, setting it to max-low
  • The starting pointer offset only

Answer: The capacity, setting it to max-low. The three-index form caps capacity at max-low, forcing the next append to reallocate.

How do you make a genuinely independent slice that won't alias the original?

copy(dst, src) into a make([]T, len(src)) gives independent storage.

How many elements does copy(dst, src) move?

  • len(src) always
  • len(dst) always
  • cap(dst)
  • min(len(dst), len(src))

Answer: min(len(dst), len(src)). copy moves min(len(dst), len(src)) elements.

For base := []int{1,2,3,4,5}, what is cap(base[1:3:3])?

  • 1
  • 2
  • 3
  • 4

Answer: 2. The full-slice expression sets cap to max-low = 3-1 = 2.

Why might a huge backing array fail to be garbage-collected?

  • Slices are never collected
  • copy leaks memory
  • A small sub-slice still references the entire backing array
  • cap is too large

Answer: A small sub-slice still references the entire backing array. A tiny sub-slice keeps the whole backing array alive; copy the part you need to free the rest.