Functions, Methods & Interfaces Best Practices
Idiomatic rules for designing functions, methods, and interfaces that stay readable under concurrency, testing, and years of module upgrades.
Search across all documentation pages
Idiomatic rules for designing functions, methods, and interfaces that stay readable under concurrency, testing, and years of module upgrades.
/v2 module paths.golangci-lint (ireturn, revive, govet) so conventions become CI signal.go fix modernizers in 1.26 may shift idioms.(T, error) with error last. Matches reader expectations and staticcheck conventions.%w and domain prefixes. fmt.Errorf("checkout: charge: %w", err) preserves errors.Is chains.return values scan faster in review.New(WithPort(8080)) scales without breaking callers.context.Context first on I/O boundaries. Never store context in structs; thread it through parameters.*T when pointer methods exist. Ensures interface satisfaction without address surprises.sync.Mutex, sync.WaitGroup, and similar fields require pointer receivers only.New or New<Type>. Document required dependencies; validate inputs before returning.io-style small surfaces beat enterprise-wide Repository interfaces.New returns *Service, not ServiceInterface.(*T, error) or untyped nil interfaces - never typed nil pointers in interfaces.func(http.Handler) http.Handler stays simpler than one-method interfaces when no mock needed.id := id or pass parameters to go func(id int){...}(id).len==0 checks, not nil slice identity. Zero-arg calls pass empty non-nil slices.go test -race on packages using goroutines with captured state. Philosophy without detection fails in production.Start with A and C for public APIs, then B for domain types, then D when adding concurrency.
Most production bugs trace to interfaces and errors, not variadic syntax.
In services with tests, yes - define the interface beside the consumer.
Single-binary tools may defer until a second implementation or mock appears.
Keep framework types at the edges.
Call domain functions returning (T, error) and map errors to HTTP status inside handlers.
Rarely - when the concrete type must stay sealed and the interface is the product (plugins, driver factories).
Document evolution policy.
Shadowing err with := so defer no longer updates the named result.
Use assignment on the outer err or avoid named results entirely.
Wrap generated clients behind one- or two-method interfaces per use case.
Do not mirror entire gRPC service interfaces in domain code.
ireturn can flag returns of interfaces from functions - tune severity to match "return structs" policy.
Consistency matters more than enabling every analyzer.
Use generics for shared algorithms; keep interfaces for runtime substitution and I/O.
Do not recreate Java-style generic repository hierarchies.
Options excel for optional tuning.
Required, validated configuration with many interdependent fields fits a struct checked once in New.
Generated reconcilers already use pointer receivers and injected clients.
Apply section C when abstracting cloud SDKs behind domain interfaces.
Closures and interfaces still apply; avoid capturing large state when memory is tight.
Verify interface calls on hardware targets at build time.
During major Go upgrades and quarterly API reviews.
Add a failing regression test when a typed-nil bug ships - link it in the team wiki.
Stack 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