# Why Arrays and Slices Matter
Most Go performance and correctness issues in data processing come from how slices share memory. If you deeply understand len, cap, append, and copy, you can avoid accidental mutation bugs and unnecessary allocations.
Arrays are useful for fixed-size semantics, but slices are the default tool in real services: parsing payloads, batching DB operations, queues, buffers, and streaming transformations.
Real-World Analogyclick to expand
Think of an array as a fixed-size warehouse shelf and a slice as a movable window over that shelf. Moving the window changes what you can see, but items are still on the same shelf unless you allocate a new one.
# Arrays: Fixed Size Value Type
Arrays have size in their type. Assigning an array copies all elements, which means two arrays are independent.
a := [5]int{10, 20, 30, 40, 50}
b := a
b[0] = 999
fmt.Println(a) // [10 20 30 40 50]
fmt.Println(b) // [999 20 30 40 50]# Slices: Header + Backing Array
A slice is a small header (pointer, len, cap). Sub-slices usually share the same backing array, so writes in one view can appear in another.
s := []int{1, 2, 3, 4}
sub := s[1:3] // [2 3]
sub[0] = 99
fmt.Println(s) // [1 99 3 4]
fmt.Println(sub) // [99 3]Breakdown: this is the most common slice pitfall in production code reviews.
# append, Capacity, and Reallocation
append may reuse the existing backing array or allocate a new one depending on capacity. Always use the returned slice because the header can change.
s := make([]int, 0, 2)
s = append(s, 1, 2)
fmt.Println(len(s), cap(s)) // 2 2
s = append(s, 3)
fmt.Println(len(s), cap(s)) // 3 4 (typically grew)# Defensive Copy Pattern
If you need isolation from caller mutation, copy the slice into a new backing array.
original := []int{1, 2, 3}
clone := make([]int, len(original))
copy(clone, original)
clone[0] = 99
fmt.Println(original) // [1 2 3]
fmt.Println(clone) // [99 2 3]# Practice Challenge
Implement Filter(s []int, fn func(int) bool) []intso it returns a new slice with matching values only. Bonus: pre-allocate when possible to reduce allocations.
package main
import "fmt"
func Filter(s []int, fn func(int) bool) []int {
// TODO: return a new slice with only elements where fn(v)==true
}
func main() {
nums := []int{1, 2, 3, 4, 5, 6}
evens := Filter(nums, func(n int) bool { return n%2 == 0 })
fmt.Println(evens) // [2 4 6]
}⚡ Key Takeaways
- Arrays are fixed-size value types; assignment copies all elements
- Slices are lightweight headers over shared backing arrays
- append may reallocate, so always use its returned slice
- Use
copyfor defensive isolation - Pre-allocate with
make([]T, 0, n)for predictable performance