In Go, an Array has a fixed length determined at compile time. A Slice is a lightweight, dynamically sized view into an underlying array. Understanding slice capacity (
cap) prevents accidental memory reallocation overhead.TL;DR (Quick Summary)#
- Array: Fixed length (
[5]int). Length is part of its type! Passing an array to a function copies the entire array memory. - Slice: Dynamic length (
[]int). Under the hood, a slice is a 3-word struct:Pointerto underlying array,Length(len), andCapacity(cap). - Slice Header: Passing a slice to a function copies ONLY the 24-byte Slice Header, not the underlying array data.
append()Reallocation: Whenlen == cap, callingappend()allocates a new, larger array (usually doubling capacity) and copies the elements over.
1. Slice Header Memory Architecture#
Under the hood in Go runtime source code, a Slice is represented by SliceHeader:
graph TD
subgraph Slice Header (24 Bytes)
DataPtr["*Data Pointer"] --> Element0
Len["Length: 3"]
Cap["Capacity: 5"]
end
subgraph UnderlyingArrayMemory ["Underlying Array Memory"]
Element0["Index 0: 'A'"]
Element1["Index 1: 'B'"]
Element2["Index 2: 'C'"]
Element3["Index 3: Allocated / Empty"]
Element4["Index 4: Allocated / Empty"]
end
2. Comparison: Arrays vs Slices#
| Feature | Array ([N]T) | Slice ([]T) |
|---|---|---|
| Size Definition | Fixed at compile time ([5]int). | Dynamic at runtime (make([]int, 5)). |
| Type Equality | [5]int and [10]int are completely different types! | []int is a single uniform type. |
| Memory Passing | Copies the whole memory block (Heavy). | Copies only the 24-byte Slice Header (Lightweight). |
| Resizing Support | Impossible. | Supported via append(). |
3. Step-by-Step Lab: Inspecting Slice len and cap Growth#
Let’s observe how append() dynamically doubles capacity when limits are reached.
Step 3.1: Writing the Slice Inspection Code#
Create main.go:
package main
import "fmt"
func inspectSlice(s []int, name string) {
fmt.Printf("%s -> len: %d, cap: %d, data: %v\n", name, len(s), cap(s), s)
}
func main() {
// 1. Create slice with make(type, len, cap)
numbers := make([]int, 0, 2) // Length 0, Capacity 2
inspectSlice(numbers, "Initial")
// 2. Append elements within capacity limit
numbers = append(numbers, 10)
inspectSlice(numbers, "Append 1")
numbers = append(numbers, 20)
inspectSlice(numbers, "Append 2")
// 3. Exceed capacity! Watch capacity DOUBLE!
numbers = append(numbers, 30)
inspectSlice(numbers, "Append 3 (Exceeded Cap)")
// 4. Slicing an existing slice [start:end]
subSlice := numbers[1:3]
inspectSlice(subSlice, "SubSlice [1:3]")
}Step 3.2: Executing the Code#
Run the application:
go run main.goExpected Terminal Output:
Initial -> len: 0, cap: 2, data: []
Append 1 -> len: 1, cap: 2, data: [10]
Append 2 -> len: 2, cap: 2, data: [10 20]
Append 3 (Exceeded Cap) -> len: 3, cap: 4, data: [10 20 30]
SubSlice [1:3] -> len: 2, cap: 3, data: [20 30]Notice how Append 3 automatically doubled the capacity from 2 to 4 to accommodate the new element!
4. Troubleshooting & Common Errors#
Error 1: panic: runtime error: index out of range#
The Cause: Accessing a slice index greater than or equal to len(slice), even if cap(slice) is large enough!
s := make([]int, 0, 10)
s[0] = 42 // 💥 Panic! len is 0.
// 🟢 Fix: Use append() or set len in make()
s := make([]int, 10, 10)
s[0] = 42 // Works perfectly!Summary & Next Steps#
In this episode:
- We analyzed fixed Arrays vs dynamic Slices.
- We dissected the 24-byte
SliceHeader(ptr,len,cap). - We observed capacity doubling during
append()reallocations.
In Episode 5: Maps & Structs, we will master Go key-value Hash Maps and custom composite Structs!

