Narrow pages by Go topic. Keep each page answerable in one fetch.
Array types - Arrays have fixed length as part of their type and use value semantics; they are not just slices with different syntax.
Assignability and representability - Whether a value can be assigned is a spec question; whether a constant fits is a representability question.
Atomics - Use atomics only when you can state the synchronization story precisely; prefer mutexes first.
Benchmarks - Write benchmarks to compare alternatives under realistic conditions, not to manufacture pretty numbers.
Blank identifier and predeclared identifiers - The blank identifier discards values; predeclared identifiers are built into the language and still obey ordinary name-binding rules.
Bridge channel - A bridge channel flattens a stream of channels into one output stream while preserving cancellation and ownership.
Buffered channels - Use buffering to encode known queueing or semaphore capacity, not to silence blocking you do not understand.
Build tags - Use build constraints to express real platform or feature differences, not as a general configuration system.
Built-in functions - Go built-ins are language-defined operations, not ordinary library functions; each has narrow operand and result rules.
Cancellation - Cancellation should propagate from the caller and stop goroutines and I/O promptly.
cgo - Use cgo only when a real interoperability need exists and contain the boundary carefully.
Channel closing - Close channels only to signal no more sends; ownership of closing must be singular and obvious.
Channels - Use channels to communicate ownership and coordination, not as a default replacement for all synchronization.
Communicating sequential processes (CSP) - Go borrows a CSP-style mental model: independent processes coordinate by communication, not incidental shared memory.
Composite literals - Composite literals construct structs, arrays, slices, and maps directly, but keyedness and element rules matter for safety and readability.
Concurrency vs parallelism - Concurrency structures work; parallelism runs work at the same time. Do not treat them as interchangeable.
Confinement - Confine mutable state to one owner and communicate with that owner instead of sharing state broadly.
Constants and iota - Understand untyped constants and `iota`; constant rules are compile-time semantics, not ordinary runtime variables.
Context values - Use context values only for request-scoped metadata that must cross API boundaries, not as a bag of optional parameters.
Conversions - Go conversions are explicit and rule-bound; they are not free-form casts that erase type semantics.
Coverage - Use coverage to find blind spots, not as a proxy for test quality; measure packages and integration paths deliberately.
Database connection pools - `sql.DB` is a concurrency-safe connection pool. Configure pool limits deliberately and keep them aligned with workload and database capacity.
Database timeouts - Database calls should usually be context-bound, cancellable, and aligned with request or job deadlines.
Deadlocks, livelocks, and starvation - Concurrent systems must make progress. Blocking, retrying, or selecting forever without progress is still failure.
Defer - Use defer for cleanup at the point resources are acquired, but understand its execution cost and timing.
Dependency injection - Prefer explicit constructor wiring and small interfaces over framework-heavy dependency injection in Go.
Docs and comments - Go doc comments are part of the API surface and should explain intent, not restate code.
Embedding - Embed types to compose behavior or promote fields deliberately, not to simulate inheritance.
Environment configuration - Read environment variables at the boundary, validate them early, and keep configuration loading separate from business logic.
errgroup - Use `errgroup` when sibling goroutines should return the first error and share cancellation.
Error comparison - Compare errors with `errors.Is` and `errors.As` when wrapping may be involved; direct equality and brittle type checks fail easily.
Error handling - Treat errors as ordinary values, return them early, and add context without obscuring control flow.
Error strings and error flow - Error text should be readable and stable, but the bigger issue is control flow: return errors directly, avoid in-band sentinels when possible, and keep failure paths easy to scan.
Error wrapping - Wrap errors when extra context helps callers and keep the original cause inspectable with `%w`.
Errors as language semantics - The predeclared `error` type and interface nil behavior are language semantics underneath Go's idiomatic error-handling style.
errors.Join - Use `errors.Join` when multiple sibling failures matter and callers still need `errors.Is` and `errors.As` to work.
Escape analysis - Let escape analysis inform allocation work, but do not contort APIs around compiler heuristics without measurement.
Execution tracing - Use execution tracing when latency depends on scheduler, blocking, GC, or network interactions that profiles alone cannot explain.
Flags and CLI structure - Simple command-line tools can rely on `flag` directly, but growing CLIs need structure that keeps parsing, validation, and command execution readable.
fmt printing - Use `fmt` deliberately: choose verbs carefully, separate formatting from logging, and prefer simple output APIs when formatting is not the point.
For statements - Go has one looping construct, but initializer scope, post statements, and range behavior all have distinct rules.
For-select loops - Long-lived channel loops should express multiplexing, shutdown, and backpressure clearly without spinning.
Functional options - Use functional options when a constructor has many optional settings and defaults should stay readable.
Fuzzing - Use fuzzing for parsers, decoders, and transformations where unexpected inputs matter more than enumerating all cases by hand.
Garbage collector - Understand allocation rate, live heap, and latency tradeoffs before tuning GC behavior.
Generics - Use generics when they simplify repeated type-safe logic; avoid them when interfaces or concrete code are clearer.
Getters - Ordinary Go getters are named after the property, not prefixed with `Get`.
Global state - Package-level state is sometimes useful, but it should be small, explicit, and hard to misuse; hidden mutable globals age badly.
go and toolchain directives - Set `go` to the minimum language expectation and use `toolchain` only when developer ergonomics need a newer default toolchain.
Go concurrency philosophy - Prefer simple, composable concurrency with explicit ownership, cancellation, and communication.
go generate - Use `go generate` for explicit developer-invoked generation steps; keep outputs reproducible and reviewable.
Go workspaces - Use `go.work` for multi-module local development, not as the published dependency contract consumers rely on.
go:embed - Use `//go:embed` for small static assets shipped with the binary; keep patterns explicit and package-local.
gofmt and formatting - Let `gofmt` define ordinary formatting. Do not spend review energy on layout choices the tool already settled.
Golden files and testdata - Use `testdata/` and golden files for stable external fixtures; make updates explicit and diffable.
GOMAXPROCS - `GOMAXPROCS` controls how many threads may execute Go code simultaneously; treat it as a measured runtime knob, not a correctness tool.
Goroutine dump debugging - When a Go process appears stuck, capture goroutine stacks and correlate them with ownership, blocking, and profile data.
Goroutine lifecycle - Every goroutine should have a clear owner, exit condition, and cancellation path.
Goroutine supervision - If long-lived goroutines may fail, restart policy, backoff, observability, and shutdown ownership must be explicit.
Goroutines - Start goroutines for independently owned work with a clear lifecycle, not as anonymous background side effects.
govulncheck - Run `govulncheck` in module-aware repos and triage reachable findings, not just package presence.
Graceful shutdown - Process shutdown should have one owner that captures signals, stops new work, and gives in-flight work a bounded time to finish.
gRPC - Use gRPC when strongly typed RPC contracts, streaming, or cross-service schema discipline clearly help.
Heartbeats - Heartbeats are periodic liveness signals. They need clear ownership, interval policy, and shutdown behavior.
HTML templates - Use `html/template` for server-rendered HTML, let autoescaping work, and keep template data explicit.
HTTP clients - Reuse clients, configure timeouts deliberately, and bind requests to context.
HTTP servers - Use the standard `net/http` server lifecycle deliberately: timeouts, shutdown, and handler ownership matter more than routing sugar.
I/O readers and writers - Design streaming APIs around `io.Reader` and `io.Writer` when data should flow incrementally instead of materializing eagerly.
Identifier naming and initialisms - Good Go names optimize readability at call sites; initialisms, receiver names, and getter style should stay consistent and unsurprising.
Import style - Import blocks should stay boring: grouped predictably, renamed rarely, and blank or dot imports used only for clear semantics.
Init functions - Keep `init` rare, local, and boring; avoid hiding application behavior in package initialization.
Integer overflow - Assume integer arithmetic can overflow silently; guard sizes, counters, and bounds math deliberately.
Interface design - Keep interfaces small and define them where they are consumed.
Interface names - Interface names should describe behavior clearly and stay small enough that the name still means something.
Interface types and method sets - Interface satisfaction depends on method sets, not intent; pointer and value receiver choices directly affect assignability.
Internal packages - Use `internal/` to enforce non-public package boundaries inside a repo when code should not be imported externally.
JSON handling - Use the standard library JSON tools carefully, validate boundaries, and avoid loose decoding by default.
Linters - Run automated analysis routinely; catching preventable mistakes early is cheaper than reviewing or debugging them later.
Literal syntax - Go literal forms for integers, floats, imaginaries, runes, and strings are precise lexical rules, not loose reader-friendly conventions.
Logging with slog - Use `log/slog` for structured logs with stable keys, contextual attributes, and clear handler boundaries.
Map internals and performance - Treat Go maps as an abstraction for correctness, but use implementation details to form measured performance hypotheses when map-heavy code is hot.
Maps - Use maps for keyed lookup and membership checks, but remember they are reference-like mutable state that need synchronization.
Memory model - Concurrent correctness depends on happens-before relationships, not intuition.
Metrics and expvar - Use `expvar` for lightweight built-in metrics when simple counters and gauges are enough, and keep metric ownership explicit.
Middleware - Middleware should wrap request handling for cross-cutting concerns without obscuring request ownership, error flow, or cancellation.
Minimal version selection - Understand that Go chooses the minimum required dependency versions in the graph, not the newest releases available.
Module compatibility - Prefer compatibility-preserving evolution and understand import versioning before breaking APIs.
Modules - Use modules as the unit of versioned distribution and route workspace, replace, private-module, and version-selection questions to narrower pages.
Named result parameters - Use named results when they genuinely clarify a small function or a defer-based cleanup path; otherwise keep returns explicit.
net/http - Prefer the standard library HTTP stack unless you have a concrete need not met by it.
new vs make - `new` allocates zeroed storage for any type; `make` initializes slices, maps, and channels into usable values.
Nil interfaces - An interface value is only nil when both its dynamic type and dynamic value are nil.
Nil vs empty slices - Choose `nil` versus empty slices deliberately at API boundaries because callers and encoders can observe the difference.
Or-channel - An or-channel closes when any input channel signals completion; use it to merge cancellation-like signals without leaking helpers.
Or-done channel - Wrap channel reads so downstream consumers stop promptly when cancellation wins instead of blocking on abandoned input.
Order of evaluation - Go does not guarantee every subexpression order people casually assume; side effects inside one expression need care.
Package and import semantics - Package clauses and import declarations are language semantics, not just style; they define names, initialization dependencies, and file cohesion.
Package layout - Organize Go code by clear package boundaries, not framework-style folders.
Panic and recover - Reserve panic for unrecoverable invariants and internal programmer errors, not normal control flow.
Pipelines - Pipeline stages should close outbound channels, respect cancellation, and avoid blocked senders.
Pointer types - Pointers model indirection and shared access, but addressability and method-set rules matter as much as nil checks.
Private modules and proxies - Configure `GOPRIVATE` and related settings intentionally so private code stays private while public modules keep proxy and checksum protections.
Profiling - Measure first with pprof and tracing before changing code for performance.
Program initialization order - Package initialization follows dependency order plus declaration dependencies; `init` behavior only makes sense in that larger model.
Queuing - Queues should express explicit backpressure and overflow policy. Capacity is a design choice, not a patch for blocked goroutines.
Race conditions - Unsynchronized conflicting access is a correctness bug. Fix the ownership or synchronization story, not just the symptom.
Race detector - Use the race detector routinely in tests; data races are correctness bugs, not minor warnings.
Range loops - Remember that `range` copies element values and evaluates the range expression once before looping.
Rate limiting - Rate limiting protects downstream capacity. Limit at the right boundary, choose burst deliberately, and honor cancellation while waiting.
Receiver choice - Choose pointer or value receivers consistently based on mutation, size, and semantics.
Reflection - Use reflection sparingly at framework or boundary layers; prefer ordinary static Go code when you can.
Replace directives - Use `replace` for deliberate local development or controlled forks; keep it explicit and temporary when possible.
Replicated requests - Replicate requests only when reduced tail latency is worth duplicate work, and cancel losers as soon as a winner is good enough.
Routing and routers - Keep routing explicit and boring: one clear ownership point for URL patterns, HTTP methods, and handler wiring.
Run-time panics - Some operations panic by language or runtime contract; these are semantic hazards distinct from choosing to call `panic` yourself.
Scope and blocks - Go is lexically scoped; whether a name exists, shadows another, or is visible at all depends on its block.
Select patterns - Use `select` to express multiplexed communication and cancellation clearly; avoid busy loops and fairness assumptions.
Selectors, indexing, and slicing - Selectors and index or slice expressions look simple, but addressability, map rules, and bounds semantics matter.
Semicolons - Most Go semicolons are inserted automatically; understanding the insertion rules explains several odd-looking syntax errors.
Slices - Treat slices as views over arrays with length, capacity, and aliasing behavior that matter in APIs.
SQL with database/sql - Use `database/sql` handles deliberately: context-aware queries, bounded lifetimes, and transaction scope that matches the unit of work.
Static file serving - Serve static files explicitly, keep the URL subtree narrow, and distinguish immutable assets from runtime configuration.
String construction - Use the simplest string-building tool that matches the job: `+` for small local cases, `fmt.Sprintf` for formatting, and `strings.Builder` for piecemeal assembly.
Strings - Treat strings as immutable byte sequences that may contain UTF-8; choose byte- or rune-oriented operations deliberately.
Struct tags - Use struct tags as narrow metadata for boundary layers, not as a hidden control plane for core domain logic.
Struct types - Struct semantics cover field identity, comparability, embedding syntax, tags, and literal shape; they matter beyond plain data grouping.
Subtests - Use subtests to structure related cases clearly and to isolate failures without duplicating test harness code.
Switch and type switch - Go switch statements are powerful but rule-bound; expression switches and type switches solve different problems.
sync.Cond - Use `sync.Cond` when goroutines wait on state transitions protected by a mutex, especially when channels fit poorly.
sync.Map - Use `sync.Map` for the narrow cases it was designed for, not as a default concurrent map replacement.
sync.Mutex - Use a mutex to protect shared mutable state when that is simpler than channel-based coordination.
sync.Once - Use `sync.Once` or `OnceValue` for one-time shared initialization; do not open-code racy lazy init.
sync.Pool - Use `sync.Pool` only for temporary reusable objects that may be dropped at any GC cycle.
sync.RWMutex - Use `sync.RWMutex` only when read-heavy contention is real and measured; `sync.Mutex` stays the default.
sync.WaitGroup - Call `Add` before starting work, pair each increment with one `Done`, and keep ownership simple.
Table-driven tests - Use table-driven tests when many cases share the same structure and assertions.
Tee channel - A tee channel duplicates each value to two downstream consumers; slow-consumer behavior and shutdown must be explicit.
Test execution modes - Use `go test` execution modes such as parallelism, shuffle, and short mode to expose hidden coupling and control scope.
Test failure messages - A failing test should tell the reader what case ran, what differed, and why the failure matters without forcing a debugger session.
Test helpers - Use `t.Helper` and small helper functions to remove repetition without hiding test intent.
Testing - Use deterministic tests with clear diagnostics, then route helpers, coverage, fixtures, and concurrency-specific questions to narrower pages.
testing/synctest - Use `testing/synctest` for deterministic tests of concurrent code that otherwise depend on timing or scheduler luck.
Tickers - Stop tickers you create, prefer timers for one-shot waits, and do not let periodic work outlive its owner.
Time durations - Express durations with explicit units or parsing; raw integers often become nanoseconds by accident.
time.After - Use `time.After` for simple one-shot waits, not hot loops where timer allocation and cleanup behavior matter.
TLS and HTTPS - HTTPS configuration should be explicit: own the server settings, certificate loading, and protocol floor instead of relying on folklore.
Type assertions - Use type assertions when runtime type variation is real, and always handle the non-matching case deliberately.
Type identity and underlying types - Namedness and underlying types decide when two types are identical, distinct, or merely similar enough for conversions and constraints.
unsafe - Use unsafe only when you can justify a measurable need and maintain strict invariants around memory assumptions.
URL query parameters - Parse query parameters at the HTTP boundary, validate them explicitly, and convert them into typed application inputs.
Variable shadowing - Avoid reusing names across nested scopes when `:=` can silently change which value later code observes.
Variables and short declarations - Use `var` and `:=` with the exact redeclaration and initialization rules in mind; short declarations do not mean loose scoping.
Vendoring - Vendor only when you need hermetic or policy-driven builds; treat `vendor/` as generated dependency snapshot, not hand-edited source.
Work stealing - Go's scheduler uses work stealing internally. Treat scheduler behavior as an implementation detail and measure before tuning around it.
Worker pools - Use worker pools when you need bounded concurrency, backpressure, and predictable lifecycle management.
Zero values - Design Go types so the zero value is useful when practical.