"A slice is not the data. It's a window into the data."


The Slice Header

A slice in Go is a 24-byte struct (on 64-bit systems) containing metadata:

// Runtime definition (simplified)
type slice struct {
    ptr unsafe.Pointer  // Points to underlying array
    len int             // Current length
    cap int             // Capacity
}

Visual Representation

Slice Header (24 bytes):
┌─────────────┬─────┬─────┐
│ ptr         │ len │ cap │
│ 0x1400...   │  3  │  3  │
└──────┬──────┴─────┴─────┘
       │
       └──→ Underlying array: [1, 2, 3]

Pass-by-Value Behavior

When you pass a slice to a function, Go copies the header, not the data:

func modify(s []int) {
    s[0] = 999  // ✓ Modifies underlying array
}

func main() {
    nums := []int{1, 2, 3}
    modify(nums)  // Copies header (24 bytes)
    fmt.Println(nums)  // [999, 2, 3]
}

Both headers point to the same underlying array.


The Append Problem

Scenario A: Reallocation (Different Arrays)

When capacity is exceeded, append allocates a new array:

func appendNoPtr(s []int) {
    s = append(s, 4)
}

func main() {
    nums := []int{1, 2, 3}  // cap = 3
    appendNoPtr(nums)
    fmt.Println(nums)  // [1, 2, 3] — unchanged!
}

What happens:

1. main():
   nums = {ptr: 0x1400, len: 3, cap: 3}

2. appendNoPtr receives COPY:
   s = {ptr: 0x1400, len: 3, cap: 3}

3. append reallocates:
   s = {ptr: 0x1580 → [1,2,3,4], len: 4, cap: 6}
       ↑ NEW array!

4. Back in main():
   nums = {ptr: 0x1400 → [1,2,3], len: 3, cap: 3}
          ↑ Still points to OLD array

Scenario B: Within Capacity (Invisible Data)