>_
GolangStepByStep
Intern

Arrays & Slices

Fixed arrays and dynamic slices — Go's most-used collection type

# 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 copy for defensive isolation
  • Pre-allocate with make([]T, 0, n) for predictable performance
practice & review