Day 009 — Slice Internals: Backing Array, append & Aliasing Gotchas¶
Month 1 · Week 2 · ⬅ Day 008 · Day 010 ➡ · Journal index
🎯 Learning Objective¶
Predict when append mutates a shared backing array vs allocates a fresh one, and avoid the classic aliasing bugs.
📚 Topics¶
appendgrowth & reallocation- Aliasing through sub-slices
- The 3-index slice
s[low:high:max]
📖 Reading / Sources¶
- Go Blog — Slices: usage and internals
- Go Blog — Arrays, slices: the mechanics of 'append'
- Go Slice Tricks (community wiki)
📝 Notes¶
appendreturns a (possibly new) slice — always reassign:s = append(s, x). Ignoring the return value is the #1 slice bug → [[append]].- If
caphas room,appendwrites in place into the shared backing array. Any other slice viewing those indices sees the change → [[slice-aliasing]]. - If
capis exhausted,appendallocates a new backing array, copies, and the result no longer aliases the original. - Growth is amortized O(1) but the exact factor is unspecified — never rely on it; rely only on the returned slice.
- Aliasing trap:
head := base[:2]; head = append(head, x)overwritesbase[2]because the capacity reached pasthead's length. - Fix: the 3-index expression
base[i:j:j]caps capacity to the length, so the nextappendis forced to allocate and cannot clobber the parent → [[three-index-slice]]. - For a truly independent copy:
dst := make([]T, len(src)); copy(dst, src). - A
nilslice and an empty non-nil slice behave the same forlen/range/append, butjson.Marshalrenders them asnullvs[].
💻 Code Examples¶
base := []int{1, 2, 3, 4, 5}
head := base[:2] // len=2, cap=5 — shares storage
head = append(head, 99) // capacity exists -> overwrites base[2]!
// base is now [1 2 99 4 5]
safe := base[1:3:3] // cap capped to len -> next append allocates
safe = append(safe, 0) // base untouched
Full code:
examples/month-01/slice-internals/main.go· Run:go run ./examples/month-01/slice-internals
🏋️ Exercises / Practice¶
| Exercise | Status | Link |
|---|---|---|
| Dedup keeping order (no input mutation) | ✅ | exercises/month-01/week-2/dedup |
🐛 Mistakes Made¶
- Wrote
append(s, x)without assigning back → the element seemed to "disappear". - Returned a sub-slice from a function and later
appended to it, silently corrupting the caller's data. Switched to a 3-index slice / copy.
❓ Open Questions¶
- Does
slices.Clip(Go 1.21+) help here? (Yes — it sets cap to len; worth adopting.)
🧠 Active Recall (answer without looking)¶
- Q: Why must you write
s = append(s, x)rather than justappend(s, x)?A
append may allocate a new backing array and returns a new header; the original s is unchanged otherwise.
2. Q: How does s[1:3:3] prevent an aliasing bug? A
The third index caps capacity to the length, so the next append cannot reuse the parent's storage and must allocate.
🪶 Feynman Reflection¶
append is like adding to a notebook page: if there's blank space (cap) it writes right there — and anyone sharing that page sees it. If the page is full, it copies everything to a fresh page and writes there, leaving the old page (and its other readers) untouched.
🕳️ Knowledge Gaps¶
- The
slicesstandard package helpers (Clip,Clone,Delete) — fold in next.
✅ Summary¶
I understand when append mutates shared storage vs reallocates, why reassignment is mandatory, and how 3-index slices and copy prevent aliasing bugs.
⏭️ Next Steps / Prep for Tomorrow¶
- Day 010: idiomatic slice tricks — filter, insert, delete, copy in place.
| Time spent | Difficulty | Confidence |
|---|---|---|
| 95 min | 🟦🟦🟦⬜⬜ | 🟦🟦🟦⬜⬜ |
Suggested commit: feat(examples): slice internals and append aliasing (day 009)