Arrays vs Slices in Go
Array: fixed, value type
arr := [3]int{10, 20, 30} // size is part of the type- Fixed size, decided at compile time.
[3]intand[4]intare different types. - Value semantics when you assign or pass to a function, the whole array is copied.
- Rarely used directly in production Go code.
Slice: dynamic, reference type
s := []int{10, 20, 30} // no size = slice- Dynamic size can grow/shrink.
- Reference semantics a slice is a 3-word header:
(pointer, length, capacity). Passing a slice does not copy the underlying data. - This is what you'll use 95% of the time.
The mental model for slices
Underlying Array: [ 10 | 20 | 30 | 40 | 50 ]
↑
Slice header: ptr=&arr[0] len=3 cap=5
s[0]=10, s[1]=20, s[2]=30 // only sees first 3A slice is just a window into an underlying array.
Iteration: both are ordered
arr := [3]int{10, 20, 30}
for i, v := range arr {
fmt.Println(i, v) // 0 10 → 1 20 → 2 30, guaranteed
}
s := []int{10, 20, 30}
for i, v := range s {
fmt.Println(i, v) // same, guaranteed in insertion order
}Both arrays and slices iterate in index order, always. No surprises there unlike maps (which are deliberately randomized in Go).
That one gotcha!
a := []int{1, 2, 3} // Slice
b := a // b points to the SAME underlying array
b[0] = 99
fmt.Println(a[0]) // 99 !! a is affectedThis bites everyone. Because b := a copies the header, not the data. To get a true copy:
b := make([]int, len(a))
copy(b, a)Mental Map
| Array | Slice | |
|---|---|---|
| Size | Fixed at compile time | Dynamic |
| Type | [3]int | []int |
| Assignment | Full copy (independent data) | Header copy (shared data) |
| Iteration order | Guaranteed | Guaranteed |
| Use in practice | Rare | Almost always |
The key insight: slices don't own data, they borrow a view into an array. Everything else flows from that.