Skip to content

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

  • append growth & reallocation
  • Aliasing through sub-slices
  • The 3-index slice s[low:high:max]

📖 Reading / Sources

📝 Notes

  • append returns a (possibly new) slice — always reassign: s = append(s, x). Ignoring the return value is the #1 slice bug → [[append]].
  • If cap has room, append writes in place into the shared backing array. Any other slice viewing those indices sees the change → [[slice-aliasing]].
  • If cap is exhausted, append allocates 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) overwrites base[2] because the capacity reached past head's length.
  • Fix: the 3-index expression base[i:j:j] caps capacity to the length, so the next append is 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 nil slice and an empty non-nil slice behave the same for len/range/append, but json.Marshal renders them as null vs [].

💻 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)

  1. Q: Why must you write s = append(s, x) rather than just append(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 slices standard 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)