Chapter 23: Modify Slice
Modify slice
Modifying slices is where most beginners get surprised (and sometimes bitten) because slices behave very differently from arrays and from lists in Python/JavaScript. The key sentence you should remember forever:
Modifying a slice almost always modifies the underlying array — and therefore can affect other slices that share the same backing array.
Today I’ll explain every realistic way to modify a slice, with clear rules, visuals (text diagrams), examples you can run, common bugs + how to avoid them, and when to choose which technique.
1. The Four Main Ways to “Modify” a Slice
| # | Operation | Syntax example | Actually changes the slice header? | Changes underlying array elements? | Most common use-case | Danger level |
|---|---|---|---|---|---|---|
| 1 | Assign to index | s[3] = 99 | No | Yes | Change single element | Low |
| 2 | Append one or more elements | s = append(s, 100, 200) | Yes (new header possible) | Yes (may reallocate) | Grow the slice dynamically | Medium |
| 3 | Reslice (change len/cap window) | s = s[:5] or s = s[2:] | Yes (new header) | No | Shrink / move window | Low–Medium |
| 4 | Copy elements from another slice | copy(dst, src) | No (only dst elements) | Yes (in dst’s backing array) | Safe duplication / overwrite | Low |
2. Visual Understanding – The Slice Header
Every slice looks like this internally (3-word struct):
|
0 1 2 3 4 5 6 7 8 9 |
slice header ├── pointer ──► [ backing array ] ├── len ──► how many elements visible right now └── cap ──► how far we can grow without new allocation |
When you do s[3] = 99 → only changes array element When you do append → may change pointer + len + cap When you do s = s[:5] → changes len (and possibly cap)
3. Detailed Examples with Output & Explanation
Create modify_slice.go and run these one by one.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
package main import "fmt" func printSlice(name string, s []int) { fmt.Printf("%-12s len=%-2d cap=%-2d ptr=%p → %v\n", name+":", len(s), cap(s), s, s) } func main() { // ──────────────────────────────────────────────── // Starting point – shared backing array // ──────────────────────────────────────────────── original := []int{10, 20, 30, 40, 50, 60, 70} fmt.Println("Initial state:") printSlice("original", original) // ──────────────────────────────────────────────── // 1. Modify single element via index // ──────────────────────────────────────────────── original[2] = 999 printSlice("after s[2]=999", original) // → only element changed, header same // ──────────────────────────────────────────────── // 2. Create two views that share the same backing array // ──────────────────────────────────────────────── left := original[:4] // [10 20 999 40] cap=7 right := original[3:] // [40 50 60 70] cap=4 printSlice("left view", left) printSlice("right view", right) // ──────────────────────────────────────────────── // 3. Modify through one view → affects others! // ──────────────────────────────────────────────── left[3] = 777 // changes original[3] printSlice("left after left[3]=777", left) printSlice("right after left change", right) // ← right[0] changed too! printSlice("original after change", original) // ──────────────────────────────────────────────── // 4. Append – may cause reallocation & new backing array // ──────────────────────────────────────────────── extra := append(left, 888, 999) printSlice("extra after append", extra) printSlice("original after append to left", original) // ↑ original usually unchanged because append reallocated // ──────────────────────────────────────────────── // 5. Safe way: append to a copy // ──────────────────────────────────────────────── safe := append([]int(nil), left...) // or make copy first safe[0] = 111 printSlice("safe copy after modify", safe) printSlice("original unchanged", original) // ──────────────────────────────────────────────── // 6. Reslice – change window without copying // ──────────────────────────────────────────────── shrink := original[:3] printSlice("shrinked view", shrink) // ──────────────────────────────────────────────── // 7. copy() – controlled overwrite // ──────────────────────────────────────────────── target := make([]int, 4) n := copy(target, original[4:]) printSlice("target after copy", target) // copies first 4 elements fmt.Printf("Copied %d elements\n", n) } |
4. The Most Important Rules to Remember
- s[i] = value → always modifies the underlying array → affects all slices that can see position i
- append(s, …) → never do append without assigning back: s = append(s, …) → if cap is enough → modifies underlying array (may affect other slices) → if cap is full → allocates new larger array → old slices remain unchanged
- s = s[:n] or s = s[m:] → only changes the slice header (pointer/len/cap) → does not copy or allocate anything → very cheap → the underlying array stays the same
- copy(dst, src) → safest way to duplicate or transfer data without shared memory surprises → copies min(len(dst), len(src)) elements → dst must have enough length already (or make it first)
5. Common Bugs & How to Avoid Them
Bug 1: Append to sub-slice overwrites unrelated data
|
0 1 2 3 4 5 6 7 8 |
data := []int{1,2,3,4,5,6,7} sub := data[:3] sub = append(sub, 99) // may overwrite data[3] ! |
Fixes:
|
0 1 2 3 4 5 6 7 8 9 10 |
// Option A – full copy sub = append([]int(nil), data[:3]...) // Option B – limit capacity from the beginning sub = data[:3:3] // cap = 3 → append will always reallocate |
Bug 2: Forgetting to assign back append result
|
0 1 2 3 4 5 6 |
append(s, 100) // ← WRONG – result discarded |
Always: s = append(s, 100)
6. Your Quick Practice Task
Try to:
- Create slice [10,20,30,40,50,60]
- Make two overlapping views: first 4 and last 4
- Change element 3 through first view → see it change in second view
- Append 3 numbers to first view → check if original / second view changed
- Fix it so append doesn’t affect other views
Which technique did you use to make it safe?
Any part still fuzzy?
- What exactly happens when append causes reallocation (growth factor)?
- Slice aliasing bugs in loops?
- append with … spread operator?
- Or ready for maps or structs next?
Keep running and breaking these examples on purpose — that’s the fastest way to really understand slices. You’re making excellent progress — slices are the heart of most Go code you’ll write. 💪🇮🇳 Let’s keep going! 🚀
