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]int and [10]int are 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

A slice is a small header of pointer, length, and capacity, pointing into a separate underlying array slice header pointer ──┐ length = 4 capacity = 6 90 85 77 92 underlying array — length 4 used, capacity 6 (unused space reserved for growth) Appending within capacity is cheap — no copy. Appending past it allocates a new, larger array.
This is why passing a slice to a function is cheap (it copies only the small header, not the data) but also why two slices can unexpectedly share the same underlying array.

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

Gotchas

Field notes