Go
Slices, Arrays & Maps
Go has both a fixed-size array (structurally close to C's arrays) and
a resizable slice built directly on top of one — in practice, almost all real Go code
uses slices, not arrays directly. Maps round out the built-in collections, Go's equivalent of
C++'s std::map.
Quick reference
- What it is
- Array: fixed-size, like C. Slice: a resizable view backed by an array, Go's default collection type. Map: built-in key-value pairs.
- Mental model
- A slice is a small header (pointer, length, capacity) pointing at an underlying array — resizing it, within limits, is cheap because it can just adjust the header without copying data.
- Key numbers
- An array's size is part of its type (
[5]intand[10]intare different types entirely) — a slice's is not, which is exactly why slices are used almost everywhere instead. - Reach for it when
- Slice: the default choice, nearly always. Array: only when the size is genuinely fixed and known at compile time. Map: looking values up by key.
Arrays — fixed size, like C
var readings [5]int // an array of exactly 5 ints, zero-valued
readings[0] = 90
scores := [3]int{90, 85, 77} // declared and initialized together
fmt.Println(len(scores)) // 3
Structurally identical to C's arrays — fixed size, contiguous memory, zero-based indexing. Go arrays are rarely used directly in practice; they mostly exist as the thing slices are built on top of.
Slices — the default collection type
scores := []int{90, 85, 77} // no size in the brackets — this is a SLICE, not an array
scores = append(scores, 92) // grows automatically — no manual resizing like C
fmt.Println(scores) // [90 85 77 92]
fmt.Println(len(scores)) // 4
sub := scores[1:3] // a slice of scores[1] and scores[2] — [85 77]
append is doing real work here — see C++'s
std::vector for the closest direct comparison: both grow automatically and both hide the
resizing mechanics from the caller.
How a slice actually works
Maps
ages := map[string]int{
"Ada": 25,
"Linus": 30,
}
ages["Grace"] = 40 // add a new key
fmt.Println(ages["Ada"]) // 25
value, ok := ages["Missing"] // the SAME (value, ok) pattern from Interfaces and Functions
if !ok {
fmt.Println("key not found")
}
Real-world applications
- API response handling — a slice of structs parsed from a JSON array response is one of the most common shapes in Go server code.
- Deduplication and lookup tables — a
map[string]booltracking "have I seen this key already" is a common, idiomatic Go pattern. - Buffer reuse — pre-allocating a slice with a known capacity (
make([]int, 0, 100)) avoids repeated reallocation in performance-sensitive code, directly mirroring C++'svector::reserve.
Gotchas
Field notes
- Two slices can share the same underlying array — modifying one through indexing can silently affect the other, a real and sometimes-surprising consequence of the header/array split shown in the diagram above.
- Accessing a Go map with a missing key doesn't panic — it returns the type's zero value, silently. Always use the two-value
value, ok := m[key]form when "key not found" needs to be distinguished from "key present with a zero value." - Map iteration order in Go is deliberately randomized on every run — code that depends on a specific iteration order is a bug, not a portability quirk; sort explicitly if order matters.
- Indexing a slice or array out of bounds panics at runtime in Go — safer than C's silent undefined behavior on the same mistake, but still a real bug to catch, just caught loudly instead of silently.