05 · Arrays, Slices & Maps¶
🎥 Video walkthrough¶
Arrays: fixed size, rarely used directly¶
An array's length is part of its type — [3]int and [5]int are different
types entirely. Because of this rigidity, arrays are uncommon in everyday Go
code; slices (below) are what you'll actually reach for.
package main
import "fmt"
func main() {
var nums [3]int // [0 0 0] -- zero-valued
nums[0] = 10
nums[1] = 20
nums[2] = 30
fruits := [3]string{"apple", "banana", "cherry"}
sized := [...]string{"a", "b"} // let the compiler count: [2]string
fmt.Println(nums, fruits, sized, len(fruits))
}
Slices: the workhorse collection¶
A slice is a flexible, growable view over an underlying array — three words under the hood: a pointer, a length, and a capacity.
package main
import "fmt"
func main() {
// Slice literal
fruits := []string{"apple", "banana", "cherry"}
fmt.Println(fruits, len(fruits)) // [apple banana cherry] 3
// make(type, length, capacity)
scores := make([]int, 0, 10) // len 0, capacity 10 -- avoids reallocation
// append grows the slice, returns a (possibly new) slice
scores = append(scores, 90, 85, 77)
fmt.Println(scores, len(scores), cap(scores))
// slicing: [low:high), high is exclusive
fmt.Println(fruits[0:2]) // [apple banana]
fmt.Println(fruits[1:]) // [banana cherry]
fmt.Println(fruits[:2]) // [apple banana]
}
Slices share underlying arrays¶
Slicing does not copy data — two slices can point at the same backing array, so mutating one can affect the other:
package main
import "fmt"
func main() {
original := []int{1, 2, 3, 4, 5}
view := original[1:4] // [2 3 4], shares memory with original
view[0] = 99
fmt.Println(original) // [1 99 3 4 5] -- original changed too!
// To get an independent copy, use copy()
independent := make([]int, len(view))
copy(independent, view)
independent[0] = -1
fmt.Println(view) // unaffected: [99 3 4]
}
Two-dimensional slices¶
package main
import "fmt"
func main() {
grid := make([][]int, 3) // 3 rows
for i := range grid {
grid[i] = make([]int, 3) // each row: 3 columns
}
grid[1][1] = 5
for _, row := range grid {
fmt.Println(row)
}
// [0 0 0]
// [0 5 0]
// [0 0 0]
}
Maps: key-value pairs¶
package main
import "fmt"
func main() {
// Map literal
ages := map[string]int{
"Alice": 30,
"Bob": 25,
}
// make() for an empty map
scores := make(map[string]int)
scores["Carol"] = 88
scores["Dave"] = 92
fmt.Println(ages["Alice"]) // 30
// the "comma ok" idiom -- check whether a key exists
value, ok := ages["Eve"]
fmt.Println(value, ok) // 0 false -- zero value if missing, ok tells you why
// delete a key
delete(scores, "Dave")
// iterate -- order is NOT guaranteed
for name, score := range scores {
fmt.Println(name, score)
}
fmt.Println(len(scores))
}
The comma-ok idiom (value, ok := m[key]) is essential: reading a missing
key returns the zero value silently, so ok is the only reliable way to
distinguish "key present with zero value" from "key absent."
How It Actually Works¶
A slice isn't the data — it's a 3-word header: {pointer, length, capacity}, 24
bytes on a 64-bit machine, passed by value everywhere. That's why appending inside a
function you called doesn't affect your slice unless the function returns the new
header: append only mutates the underlying array in place when there's spare
capacity; once len == cap, Go allocates a new backing array (roughly doubling
capacity below 256 elements, then growing ~1.25x for larger slices as of recent Go
versions) and copies every element over, which is why appending in a loop without
pre-sizing (make([]T, 0, n)) causes O(log n) reallocations and O(n) total copies —
still amortized O(1) per append, but with real memory churn. Maps are hash tables
implemented as an array of buckets (runtime.hmap / bmap), each bucket holding up
to 8 key-value pairs plus overflow pointers; a lookup hashes the key, picks a bucket
from the low bits of the hash, then linearly scans that bucket's 8 slots comparing
the high bits first as a fast filter. Map iteration order is deliberately
randomized by the runtime specifically to stop code from ever depending on it.
Cheat sheet¶
| Operation | Syntax |
|---|---|
| Array (fixed size) | var a [3]int |
| Slice literal | s := []int{1, 2, 3} |
| Slice with make | s := make([]int, len, cap) |
| Append | s = append(s, 4, 5) |
| Slice a slice | s[1:3] |
| Copy independently | copy(dst, src) |
| Map literal | m := map[string]int{"a": 1} |
| Map with make | m := make(map[string]int) |
| Read + existence check | v, ok := m[key] |
| Delete a key | delete(m, key) |
| Length (slice or map) | len(s) |
🔀 See this in another language¶
Exercise¶
Write a program that builds a map[string]int counting word frequency in a
[]string of words (some repeated). Iterate the map to print each word and
its count, then use append to build a []string slice containing only the
words that appear more than once, using the comma-ok idiom where useful.