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.
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
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
- Reading a missing key returns the zero value (no panic):
m["nope"]→0/"". Usev, ok := m[k]to tell "absent" from "zero". - Writing to a
nilmap panics. You mustm := make(map[string]int)(or a literal) before writing. (Contrast: a nil slice is fine to append.) - Iteration order is randomised on purpose — never rely on it.
Your code leans on all of this
type Emails []*Emailstartsnil;Add()does*u = append(*u, e)— the return-value discipline, on a nil slice (spike/.../domain/model/email.go).- A dynamic
UPDATEbuildscol = $Nby ranging amap[string]interface{}(infrastructure/repositories/email.go:138-145). The column order is random each call — it's correct only because the value is appended in the same loop iteration. "Relies on paired append, not order." sliceutils.Map/Filterreturn new slices, side-stepping aliasing entirely — the safe functional style.
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.
Check yourself (from memory)
Q1. You copy a slice, then set copy[0] = 99. The original slice…
Q2. Writing to a nil map…
make it first. A nil slice, by contrast, appends fine.
Q3. Why write s = append(s, x) and use the result?
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.)1. Effective Go — slices; Go by Example — slices.
2. 100 Go Mistakes — slice length/capacity & aliasing.