CodeRigor

FREE LESSON

Slices, maps & shared storage

Separate a slice descriptor from its backing array; build independent snapshots and deterministic map reports.

Python lists and C++ vectors can make a Go slice look deceptively familiar. A slice carries a reference to a backing array plus a length and capacity. Copying the slice value copies that descriptor; it does not clone the elements. Two descriptors can therefore expose overlapping storage. An array value, such as [3]int, instead includes its elements, and its length is part of its type.

Changing an element through one overlapping slice is visible through the other. append returns a new descriptor and may reuse capacity or allocate another array. Always retain its returned slice. Do not depend on an implementation's capacity-growth formula, and do not use an observed allocation as an ownership guarantee. A function promising a snapshot must make its independence deliberate even when the input has ample spare capacity.

Copy the elements for a snapshot
start / 4
Both descriptors may read equal values; only the copied array provides independent element storage.
Copy the elements for a snapshot
Both descriptors may read equal values; only the copied array provides independent element storage.

Choose the depth of a copy

For a slice of ints, make plus copy gives independent element storage. For a slice containing pointers, maps or slices, copying the elements still shares what those elements refer to. State the ownership policy at the depth your API actually needs. The build deliberately uses ints so its independence guarantee is precise and testable in both directions.

Go
package main
import "fmt"
func main() {
    xs := []int{3, 7}
    alias := xs
    snapshot := make([]int, len(xs))
    copy(snapshot, xs)
    alias[0] = 9
    fmt.Println(xs, snapshot)
}

The result is [9 7] [3 7]. A slice can be nil while its length is zero. Appending to nil is valid. An allocated empty slice also has length zero but need not be nil; later JSON serialization can distinguish these representations. The contract may choose one explicitly.

Aliasing changes the original
start / 2
alias and xs share row 0; the explicit snapshot keeps its own row.
Aliasing changes the original
alias and xs share row 0; the explicit snapshot keeps its own row.

Maps separate lookup from presentation

A missing map key returns the value type's zero value. For counts that makes counts[word]++ convenient after initialization. Use the comma-ok lookup when absence must be distinguished from a stored zero. A nil map supports reads but cannot accept a new entry; initialize writable maps with make or a literal. Assigning a map value shares its entries rather than copying them.

Map iteration has unspecified order. Tests and reports that require order must extract keys into a separate slice and sort it. Sorting that slice does not rearrange or mutate the map. Preserve keys whose count happens to be zero: a report of all keys is different from a report of positive counts.

The build combines an independent SnapshotAdd with fresh Frequencies maps and SortedKeys. Test spare-capacity input, mutation through both views and repeated calls. The two bugs distinguish accidental aliasing from lost multiplicity. Their failures come from observable contract violations, not timing or a particular hash-map traversal order.

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →

Focus on the data-structure examples and differences from Python containers. Follow the current Go slice/map references for exact storage and iteration rules.