Go Fundamentals Best Practices
Core habits every Go developer should internalize first.
Search across all documentation pages
Core habits every Go developer should internalize first.
These rules reinforce the fundamentals section: clear names, explicit error handling at boundaries, and collections used with their real semantics.
gofmt, go vet, and golangci-lint to catch violations automatically.i and err are fine in loops; exported APIs deserve descriptive names.:= inside functions when the type is obvious. Reserve var for package-level declarations and intentional zero values.iota. Avoid magic numbers scattered in logic; name states and flags explicitly.make or literals before writes. Never assign to a nil map.len(string) as byte length, not character count. Use range or utf8.RuneCountInString for user-visible text.make([]T, 0, n) reduces allocations in hot loops.append. s = append(s, x) because append may return a new header.main thin; put logic in importable packages. One module can host cmd/ entrypoints and library code side by side.internal/ for code that must not leak across modules. Let the compiler enforce boundaries instead of comments alone.go fmt ./... before every commit. Consistent formatting removes style debates from review.go test ./... and go vet ./... in CI. Fundamentals mistakes should fail fast in automation.go.mod and commit go.sum. Reproducible builds start with a clean module graph.No - small structs are cheaper by value.
Use pointers for mutation, optional fields, or proven copy cost.
Constants and read-only vars are fine.
Mutable package state needs synchronization and hurts testability - avoid unless wrapping sync primitives by design.
Rarely - fixed wire sizes, cryptographic blocks, or embedded constraints.
Default to slices for application code.
Embed when the outer type genuinely exposes the embedded behavior (Server logs via embedded Logger).
Use named fields when the relationship is has-a, not behaves-as.
append may reallocate and return a different slice header.
Ignoring the return leaves you pointing at stale capacity or data.
Named returns help documentation in small functions; avoid when they obscure clarity.
Fundamentals code should favor explicit return x, err.
Pass loop variables as function parameters.
On Go 1.22+, per-iteration variables reduce the risk but explicit parameters stay clearest.
cmd/<app>/main.go holds executables; library code lives in importable packages at module root or pkg/ by team convention.
nil slices often encode as null; empty slices as [].
Pick intentionally for API contracts and document the choice.
Whenever code must not be imported by other modules - helpers tied to one product, not a public SDK.
Only when validation or invariants are required.
Prefer useful zero values for simple configuration structs.
Enable linters like govet, staticcheck, and errcheck to enforce many of these habits automatically in CI.
make patternsStack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 19, 2026