Collections: Arrays, Slices, and Maps

Go has three main collection types: arrays (fixed-size), slices (dynamic), and maps (key-value). Slices are by far the most commonly used.

Arrays

Arrays have a fixed size that's part of the type. [3]int and [5]int are different types.

Declaring Arrays

// Declaration with zero values
var arr [5]int  // [0, 0, 0, 0, 0]

// Declaration with initialization
arr := [5]int{1, 2, 3, 4, 5}

// Let compiler count
arr := [...]int{1, 2, 3, 4, 5}  // Size is 5

// Specific indices
arr := [5]int{0: 10, 4: 50}  // [10, 0, 0, 0, 50]

Working with Arrays

arr := [5]int{1, 2, 3, 4, 5}

// Access elements
fmt.Println(arr[0])  // 1
fmt.Println(arr[4])  // 5

// Modify elements
arr[0] = 10

// Length
len(arr)  // 5

// Iterate
for i, v := range arr {
    fmt.Printf("Index: %d, Value: %d\n", i, v)
}

// Arrays are values (copied on assignment)
arr2 := arr      // Creates a copy
arr2[0] = 100    // Doesn't affect arr

Multidimensional Arrays

// 2D array (3 rows, 4 columns)
var matrix [3][4]int

// With initialization
matrix := [2][3]int{
    {1, 2, 3},
    {4, 5, 6},
}

// Access
matrix[0][1]  // 2

Arrays are rarely used directly. Slices are preferred for most use cases.

Slices

Slices are dynamic, flexible views into arrays. They're the workhorse of Go collections.

Creating Slices

// From array
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4]  // [2, 3, 4]

// Slice literal (creates underlying array)
slice := []int{1, 2, 3, 4, 5}

// Using make (length, optional capacity)
slice := make([]int, 5)       // len=5, cap=5, [0,0,0,0,0]
slice := make([]int, 3, 10)   // len=3, cap=10

// Nil slice
var slice []int  // nil, len=0, cap=0

Slice Internals

A slice is a struct with three fields:

  • Pointer: to the first element
  • Length: number of elements
  • Capacity: max elements before reallocation
slice := make([]int, 3, 5)
len(slice)  // 3
cap(slice)  // 5

Slicing Syntax

arr := [5]int{1, 2, 3, 4, 5}

arr[1:4]   // [2, 3, 4] - elements 1 to 3
arr[:3]    // [1, 2, 3] - first 3 elements
arr[2:]    // [3, 4, 5] - from index 2 to end
arr[:]     // [1, 2, 3, 4, 5] - entire array

// Full slice expression (with capacity)
arr[1:3:4] // [2, 3] with cap=3 (4-1)

Appending to Slices

slice := []int{1, 2, 3}

// Append single element
slice = append(slice, 4)  // [1, 2, 3, 4]

// Append multiple elements
slice = append(slice, 5, 6, 7)  // [1, 2, 3, 4, 5, 6, 7]

// Append another slice
other := []int{8, 9, 10}
slice = append(slice, other...)  // Spread operator

Important: append may return a new slice if capacity is exceeded:

slice := make([]int, 3, 3)
slice[0], slice[1], slice[2] = 1, 2, 3

// Must assign result back!
slice = append(slice, 4)  // New underlying array allocated

Copying Slices

src := []int{1, 2, 3}
dst := make([]int, len(src))

copied := copy(dst, src)  // Returns number of elements copied
// dst is now [1, 2, 3]

// Copy to smaller slice
small := make([]int, 2)
copy(small, src)  // small is [1, 2]

Common Slice Operations

// Remove element at index i
slice = append(slice[:i], slice[i+1:]...)

// Insert element at index i
slice = append(slice[:i], append([]int{value}, slice[i:]...)...)

// Prepend
slice = append([]int{value}, slice...)

// Pop last element
last := slice[len(slice)-1]
slice = slice[:len(slice)-1]

// Clear slice (keep capacity)
slice = slice[:0]

// Check if empty
if len(slice) == 0 {
    // empty
}

Slice Gotchas

// Slices share underlying array!
arr := []int{1, 2, 3, 4, 5}
slice1 := arr[1:4]   // [2, 3, 4]
slice2 := arr[2:5]   // [3, 4, 5]

slice1[1] = 100      // Modifies arr[2]
// arr is now [1, 2, 100, 4, 5]
// slice2 is now [100, 4, 5]

// To avoid, make a copy
slice1 := make([]int, 3)
copy(slice1, arr[1:4])

Nil vs Empty Slice

var nilSlice []int         // nil
emptySlice := []int{}      // not nil, but empty
emptyMake := make([]int, 0) // not nil, but empty

// All have len 0, but nilSlice == nil is true
// Behavior is usually the same
fmt.Println(len(nilSlice))    // 0
fmt.Println(len(emptySlice))  // 0

// Both work with append
nilSlice = append(nilSlice, 1)     // Works!
emptySlice = append(emptySlice, 1) // Works!

Maps

Maps are hash tables: unordered key-value collections.

Creating Maps

// Using make
m := make(map[string]int)

// Map literal
m := map[string]int{
    "alice": 25,
    "bob":   30,
}

// Empty map literal
m := map[string]int{}

// Nil map (can read, but writes panic!)
var m map[string]int  // nil

Basic Operations

m := make(map[string]int)

// Set values
m["alice"] = 25
m["bob"] = 30

// Get values
age := m["alice"]  // 25

// Missing key returns zero value
age := m["unknown"]  // 0

// Check if key exists
age, exists := m["alice"]
if exists {
    fmt.Println("Alice is", age)
}

// Common pattern
if age, ok := m["alice"]; ok {
    fmt.Println("Found:", age)
}

// Delete
delete(m, "alice")

// Length
len(m)  // Number of key-value pairs

Iterating Maps

m := map[string]int{"a": 1, "b": 2, "c": 3}

// Iterate over key-value pairs
for key, value := range m {
    fmt.Printf("%s: %d\n", key, value)
}

// Keys only
for key := range m {
    fmt.Println(key)
}

// Note: iteration order is random!

Map with Struct Values

type Person struct {
    Name string
    Age  int
}

people := map[string]Person{
    "alice": {Name: "Alice", Age: 25},
    "bob":   {Name: "Bob", Age: 30},
}

// Access
alice := people["alice"]
fmt.Println(alice.Name)

// Cannot modify struct fields directly
// people["alice"].Age = 26  // ERROR!

// Must reassign entire struct
alice := people["alice"]
alice.Age = 26
people["alice"] = alice

// Or use pointer values
peoplePtr := map[string]*Person{
    "alice": {Name: "Alice", Age: 25},
}
peoplePtr["alice"].Age = 26  // Works!

Maps as Sets

Go doesn't have a built-in set type. Use a map with bool or empty struct:

// Using bool
seen := make(map[string]bool)
seen["apple"] = true

if seen["apple"] {
    fmt.Println("Already seen apple")
}

// Using empty struct (more memory efficient)
type void struct{}
set := make(map[string]void)
set["apple"] = void{}

if _, exists := set["apple"]; exists {
    fmt.Println("Already in set")
}

Map Key Requirements

Keys must be comparable (support ==):

  • ✅ Strings, numbers, bools, pointers, arrays, structs (if fields are comparable)
  • ❌ Slices, maps, functions
// Valid
map[string]int{}
map[[2]int]string{}
map[Point]string{}  // Point{X, Y int}

// Invalid
// map[[]int]string{}  // Slices can't be keys

Generics with Collections (Go 1.18+)

// Generic function for any slice type
func Filter[T any](slice []T, test func(T) bool) []T {
    var result []T
    for _, v := range slice {
        if test(v) {
            result = append(result, v)
        }
    }
    return result
}

// Usage
nums := []int{1, 2, 3, 4, 5}
evens := Filter(nums, func(n int) bool { return n%2 == 0 })

// Generic map function
func Map[T, U any](slice []T, transform func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = transform(v)
    }
    return result
}

// Usage
doubled := Map(nums, func(n int) int { return n * 2 })

Common Patterns

Filter In Place

func filterInPlace(slice []int, keep func(int) bool) []int {
    n := 0
    for _, v := range slice {
        if keep(v) {
            slice[n] = v
            n++
        }
    }
    return slice[:n]
}

Deduplicate Slice

func unique(slice []int) []int {
    seen := make(map[int]bool)
    result := make([]int, 0, len(slice))
    for _, v := range slice {
        if !seen[v] {
            seen[v] = true
            result = append(result, v)
        }
    }
    return result
}

Reverse Slice

func reverse(slice []int) {
    for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 {
        slice[i], slice[j] = slice[j], slice[i]
    }
}

Group By

func groupBy(people []Person, keyFunc func(Person) string) map[string][]Person {
    result := make(map[string][]Person)
    for _, p := range people {
        key := keyFunc(p)
        result[key] = append(result[key], p)
    }
    return result
}

// Usage: group by age bracket
byAgeGroup := groupBy(people, func(p Person) string {
    if p.Age < 30 { return "young" }
    return "adult"
})

Next Steps

Continue to 06-structs-methods.md to learn about structs and methods.