Lesson 4 · Foundations

Slices & maps

The "copied but shared" types — where Lesson 3's rule has an asterisk.

Your win: explain why a slice is copied yet still shares its data, predict the aliasing and nil-map traps, and know why you always write s = append(s, x).

A slice is a small header over a backing array

A slice value is just three words — a pointer to a backing array, a length, and a capacity.1 When you copy or pass a slice, you copy those three words — but the pointer still aims at the same backing array. That's the asterisk on Lesson 3's "everything is copied": the header is copied, the data is shared.

a := []int{10, 20, 30} header: {ptr→[10 20 30], len 3, cap 3} b := a header COPIED, ptr still → same array b[0] = 99 → a is now {99, 20, 30} ← shared backing array!

append: why you must use the return value

append adds to a slice — but if the backing array is full (len == cap), it allocates a new, bigger array and returns a slice pointing at it. So the returned slice may be different from the one you passed in. Always capture it:

s = append(s, x)   // ✅ the ONLY safe form
append(s, x)       // ❌ result discarded — new element may vanish
The classic interview bug Pass a slice to a function that appends to it, and the caller often does not see the new element — because the caller's own header still has the old len (and append may have reallocated). But mutating an existing element is visible (shared array). Confusing the two is the #1 Go slice mistake.2

Maps are reference-like too — with a sharp edge

Your code leans on all of this

Anchors
Do this next

Go by Example — Slices & Maps

Tiny runnable programs for each behaviour. Then read Effective Go's slices section for the internals, and 100 Go Mistakes for the aliasing traps.

gobyexample.com/slices · /maps
Effective Go — slices

Check yourself (from memory)

Q1. You copy a slice, then set copy[0] = 99. The original slice…

The copied header still points at the same backing array, so element writes are shared. That's slice aliasing.

Q2. Writing to a nil map…

Reading a nil map is fine (zero values), but writing panics — make it first. A nil slice, by contrast, appends fine.

Q3. Why write s = append(s, x) and use the result?

When cap is exceeded, append allocates a new backing array and returns a new header. Ignore it and you lose the growth.
You pass a slice to a func that appends to it. Sometimes the caller sees the new element, sometimes not. Why?
recall, then click to reveal
The caller's slice header (its len) doesn't change when the callee appends — and append may reallocate to a brand-new array the caller doesn't point at. So the caller never reliably "sees" appended elements; that's why append's result must be returned/assigned. (Mutating an existing element is always visible, because the backing array is shared.)
🎓 That's Part 1 — Go's value model You can now reason about what Go copies (everything) and what it shares (pointers explicitly; slices/maps via their headers). This is the foundation the whole language stands on. Part 2 builds up: methods & receivers, interfaces (the nil-interface gotcha), embedding, and generics.
Ready for Part 2 (methods, interfaces & generics), or want a quick mixed quiz across Lessons 1–4 first? Ask me.

1. Effective Go — slices; Go by Example — slices.

2. 100 Go Mistakes — slice length/capacity & aliasing.