← GophersCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to Gophers
Snapshot Sep 30, 2026 · 23:14 UTC · version 0.1.0
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"description": "Use when choosing or operating on Go slices, maps, arrays, strings, or container/* types — including slice internals, capacity growth, preallocation, map buckets, sets via map[T]struct{}, strings.Builder vs bytes.Buffer, generic containers, and the slices/maps standard packages (Go 1.21+). Apply proactively whenever data is being collected, transformed, or copied, even if the user has not asked about allocation.",
"included_files": [
{
"relative_path": "agents/openai.yaml",
"size_in_bytes": 235
},
{
"relative_path": "references/containers-and-pointers.md",
"size_in_bytes": 4883
},
{
"relative_path": "references/slices-and-maps.md",
"size_in_bytes": 4933
},
{
"relative_path": "references/strings-bytes-builder.md",
"size_in_bytes": 4328
}
],
"name": "go-data-structures",
"skill_md_contents": "---\nname: go-data-structures\ndescription: \"Use when choosing or operating on Go slices, maps, arrays, strings, or container/* types — including slice internals, capacity growth, preallocation, map buckets, sets via map[T]struct{}, strings.Builder vs bytes.Buffer, generic containers, and the slices/maps standard packages (Go 1.21+). Apply proactively whenever data is being collected, transformed, or copied, even if the user has not asked about allocation.\"\nlicense: MIT\ncompatibility: \"Designed for Claude Code or similar AI coding agents. slices/maps packages need Go 1.21+; iterator helpers need 1.23+; weak.Pointer needs 1.24+.\"\nallowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(golangci-lint:*)\n---\n\n# Go Data Structures\n\nPick the structure that fits the access pattern — not the most familiar one. Slices and maps are the workhorses; arrays, container types, and the `slices`/`maps` packages cover the rest. Understanding the **header layout**, **growth costs**, and **copy semantics** of each turns most performance questions into one-line decisions.\n\n## Core Rules\n\n1. **Slices and maps are reference types** — assigning copies the header, not the data. Use `slices.Clone` / `maps.Clone` for a true copy.\n2. **Preallocate** with `make([]T, 0, n)` and `make(map[K]V, n)` whenever the size is known or estimable.\n3. **Always assign the result of `append`** — the backing array may move.\n4. **Use `slices` and `maps` packages** (Go 1.21+) instead of hand-rolled helpers.\n5. **`map[K]struct{}` is the canonical set** — zero-byte values, no boolean ambiguity.\n6. **`strings.Builder` for string building**, `bytes.Buffer` when you need `io.Reader`/`io.Writer`.\n\n## Picking a Structure\n\n```\nWhat do you need?\n├─ Ordered, fixed compile-time size → [N]T array\n├─ Ordered, dynamic size → []T slice\n│ ├─ Known size → make([]T, 0, n)\n│ └─ JSON output must be [] → []T{} literal (not nil)\n├─ Key/value lookup → map[K]V\n│ ├─ Need a set → map[K]struct{}\n│ └─ Known size → make(map[K]V, n)\n├─ Priority queue / top-k → container/heap\n├─ Frequent middle insertion → container/list\n├─ Fixed-size rolling window → container/ring\n├─ Pure string building → strings.Builder\n└─ Read+write of bytes → bytes.Buffer\n```\n\n## Slice Internals\n\nA slice is a 3-word header: pointer, length, capacity. Multiple slices can alias the same backing array — `s[1:4]` shares memory with `s`.\n\n### Capacity Growth\n\nThe exact algorithm has changed across versions; do **not** rely on it. As of recent Go:\n\n- `len < 256` → capacity roughly doubles.\n- `len ≥ 256` → grows by ~25%.\n- Each growth allocates a new backing array and copies — O(n) per growth.\n\n### Preallocation\n\n```go\nusers := make([]User, 0, len(ids)) // exact size\nresults := make([]Result, 0, estimated) // approximate\ns = slices.Grow(s, additional) // pre-grow before bulk append (Go 1.21+)\n```\n\n### `slices` Package (Go 1.21+)\n\n| Function | Purpose |\n|---|---|\n| `Sort`, `SortFunc`, `SortStableFunc` | sorting |\n| `BinarySearch`, `BinarySearchFunc` | sorted lookup |\n| `Contains`, `Index`, `IndexFunc` | search |\n| `Compact`, `CompactFunc` | dedupe adjacent equals |\n| `Clone`, `Equal` | safe copy / comparison |\n| `Delete`, `DeleteFunc` | removal preserving order |\n| `Grow` | preallocate before append |\n| `Concat` (1.22+) | concatenate slices |\n\nPrefer these over hand-rolled loops — they're tested, generic, and use the fastest available paths.\n\n> Read [references/slices-and-maps.md](references/slices-and-maps.md) for capacity growth, aliasing pitfalls, and 2-D slice patterns.\n\n## nil vs Empty Slice: The JSON Trap\n\nBoth have `len == 0` and `cap == 0`, but they encode differently:\n\n```go\nvar nilSlice []string // → JSON: null\nemptySlice := []string{} // → JSON: []\n```\n\nAPI contracts almost always want `[]`. **Initialise the slice explicitly** in any struct that gets marshaled to JSON, and treat nil/empty as identical when *reading* (use `len(s) == 0`).\n\nFor internal computation where nil is never marshaled, the nil slice is conventional and slightly cheaper (no allocation until first append).\n\n## Maps\n\nMaps are hash tables with 8-entry buckets and overflow chains. They are reference types — assigning a map copies a pointer.\n\n### Preallocation\n\n```go\nm := make(map[string]*User, len(users)) // avoids rehashing during population\n```\n\nThe size hint is *approximate* (it's about bucket count), but it still saves repeated rehashing in the common case.\n\n### Sets\n\n```go\ntype Set[T comparable] map[T]struct{}\n\nfunc (s Set[T]) Add(v T) { s[v] = struct{}{} }\nfunc (s Set[T]) Has(v T) bool { _, ok := s[v]; return ok }\nfunc (s Set[T]) Remove(v T) { delete(s, v) }\n```\n\n`struct{}` is zero bytes; the set is just the key set of the underlying map.\n\n`map[K]bool` is also common but ambiguous: did `false` mean \"explicitly excluded\" or \"not present\"? `struct{}` removes the question.\n\n### `maps` Package (Go 1.21+)\n\n`Clone`, `Equal`/`EqualFunc`, `DeleteFunc`; `Keys`, `Values`, `Collect`, `Insert` since 1.23 (iterators).\n\n> Read [references/strings-bytes-builder.md](references/strings-bytes-builder.md) for string-vs-bytes, `Builder` vs `Buffer`, and rune handling.\n\n## Arrays\n\nFixed-size, value type, copied on assignment. Useful for compile-time-known sizes:\n\n```go\ntype Digest [32]byte\ntype IP4 [4]byte\ncache := map[[2]int]Result{} // arrays are comparable → usable as map keys\n```\n\nFor anything dynamic, use a slice.\n\n## container/* and Third-Party\n\n| Package | Use case | Caveat |\n|---|---|---|\n| `container/heap` | priority queue, top-K | implement the interface yourself |\n| `container/list` | LRU, frequent middle splice | poor cache locality |\n| `container/ring` | rolling window, round-robin | fixed size |\n| `bufio` | I/O with many small reads/writes | always check `Flush` errors |\n\nFor typed sets/queues/trees beyond the stdlib, prefer well-tested libraries (`emirpasic/gods`, `gammazero/deque`) and benchmark before optimising.\n\n> Read [references/containers-and-pointers.md](references/containers-and-pointers.md) for heap implementation, `unsafe.Pointer`'s six valid patterns, and `weak.Pointer[T]`.\n\n## Copy Semantics Cheat Sheet\n\n| Type | Copy behaviour |\n|---|---|\n| primitives, arrays, structs | value (deep for contained value fields) |\n| slice | header copied, backing array shared — use `slices.Clone` |\n| map, channel | reference copied — use `maps.Clone` for maps |\n| `*T`, `interface` | address / (type, value) pair copied |\n\n## Anti-Patterns\n\n| Anti-pattern | Why it hurts | Do this instead |\n|---|---|---|\n| `s := append(s, x)` ignoring return | Backing array may move; `s` becomes stale | Always reassign |\n| `var m map[K]V; m[k] = v` | nil map panic | `m := make(map[K]V)` or `map[K]V{}` |\n| `var s []T` then marshal to JSON as `[]` | Encodes as `null` | `s := []T{}` |\n| `make([]T, 0, 10000)` \"just in case\" | Wasted memory | Size by actual data |\n| `m := map[K]bool{}` as a set | `false` is ambiguous | `map[K]struct{}` |\n| `bytes.Buffer` for pure string building | Extra copy in `String()` | `strings.Builder` |\n| Large struct values in a map | Each lookup copies the value | `map[K]*V` |\n\n## Verification Checklist\n\n- [ ] Every `make([]T, ...)` and `make(map[K]V, ...)` has a capacity hint when the size is known.\n- [ ] Every `append` reassigns its result.\n- [ ] Slices marshaled to JSON are initialised as `[]T{}`, not `var s []T`.\n- [ ] All \"sets\" use `map[K]struct{}` (or a generic `Set[T]` wrapper).\n- [ ] No `bytes.Buffer` used purely for `String()` output.\n- [ ] No `*sync.Mutex` copied via struct assignment (`go vet copylocks`).\n- [ ] `slices.Clone` / `maps.Clone` used when handing data to callers that may mutate.\n\n## References\n\n- [references/slices-and-maps.md](references/slices-and-maps.md) — internals, growth, aliasing, `slices`/`maps` packages\n- [references/strings-bytes-builder.md](references/strings-bytes-builder.md) — `strings.Builder`, `bytes.Buffer`, rune handling\n- [references/containers-and-pointers.md](references/containers-and-pointers.md) — `container/heap`, generic wrappers, `unsafe.Pointer`, `weak.Pointer`\n"
}SHA-256 of public snapshot: 8c26d168fde13ea4be535719ad6b160b4c4e2d0aa7c265845bc7759fad7d5e47