Standard Library Best Practices
Prefer stdlib until a clear gap; know deprecation paths.
Apply these rules in code review, module templates, and dependency ADRs so every Go service gets predictable upgrades and smaller attack surfaces.
How to Use This List
- Tick rules as your org template adopts them.
- Tie Tier A items to CI checks (
go vet,staticcheck, import guards) where possible. - Document justified third-party imports with the stdlib gap they fill.
- Revisit when Go minor releases add new stdlib packages (slog, rand/v2, ServeMux patterns).
A - Dependency discipline
- Default to stdlib for HTTP, JSON, testing, and sync primitives. Fewer modules mean faster
go mod tidyand smaller SBOMs. - Add a third-party module only with a written gap statement. Prevents duplicate routers and loggers in the same binary.
- Pin driver versions separately from application code. SQL and cloud drivers update on their own cadence.
- Avoid replacing stdlib security primitives with unmaintained forks.
crypto/tlsandcrypto/randtrack Go security releases. - Run
govulncheckon modules that duplicate stdlib roles. Catches known issues in overlapping HTTP or JSON libraries.
B - I/O and resources
- Stream with
io.Copyand bound untrusted input withio.LimitReader. Prevents OOM on uploads and malicious bodies. - Close files, rows, and response bodies on every path. Use deferred closures that capture close errors on writers.
- Use
filepathfor OS paths andpathonly for URL-like slashes. Windows deployments break on hardcoded/. - Prefer
bufio.Scannerfor line input; set buffer caps for long tokens. Avoids silentErron wide log lines. - Walk directories with
filepath.WalkDirinstead of legacyWalk. Cuts extraStatsyscalls on large trees.
C - Time and context
- Store UTC in databases; convert with
time.Locationat display. DST-safe instants across regions. - Pass
context.Contextas the first parameter on blocking APIs. Enables HTTP and RPC cancellation uniformly. - Call
cancel()fromWithTimeoutandWithCancelvia defer. Releases timer resources promptly. - Replace naked
time.Sleepin servers withselectonctx.Done(). Shutdown hooks finish within grace periods. - Stop timers and tickers when loops exit. Prevents goroutine leaks in long-running processes.
D - Logging and text
- Use
log/slogfor new services with JSON handlers in production. Operators parse levels and attributes reliably. - Compile regex and templates once at startup. Per-request
MustCompilewastes CPU under load. - Render browser HTML with
html/template, nottext/template. Contextual escaping blocks XSS from user fields. - Redact tokens and PII from structured log attributes. Compliance and incident response require safe defaults.
- Reserve package
logfor legacy boundaries until migrated. Mixed styles are fine short term; plan slog atmain.
E - Crypto, randomness, and compression
- Generate secrets with
crypto/rand, nevermath/rand/v2. Predictable IDs become account takeover vectors. - Use
math/rand/v2with explicitRandvalues in libraries. Removes hidden global state and flaky parallel tests. - Close
gzip.Writerto flush footers before uploading archives. Truncated gzip breaks downstream consumers. - Set TLS
MinVersionto 1.2 or higher on listeners. Documents cipher posture for security review. - Skip gzip on tiny HTTP bodies where headers dominate. Saves CPU on health checks and small JSON payloads.
F - SQL and networking
-
PingContextdatabases during startup before accepting traffic. Surfaces bad DSNs at deploy time, not on first user. - Set
SetMaxOpenConnsandSetConnMaxLifetimefrom DB capacity math. Protects shared database instances from one service. - Use
QueryContextandExecContextin HTTP handlers. Honorsr.Context()deadlines automatically. - Configure dedicated
http.Clientinstances with timeouts per upstream. Never rely onhttp.DefaultClientin servers. - Set
ReadHeaderTimeouton every productionhttp.Server. Blocks slowloris stalls before handlers execute.
G - Runtime and observability
- Expose pprof only on admin interfaces or loopback. Heap dumps may contain secrets and session data.
- Label hot paths with
pprof.Dofor attributable flame graphs. Otherwise profiles show anonymous runtime frames. - Align
debug.SetMemoryLimitwith Kubernetes memory limits. Leave headroom for stacks and off-heap allocations. - Enable mutex/block profiling briefly during incidents. Continuous full sampling skews latency.
- Correlate slog request IDs with profile capture timestamps. Speeds root-cause analysis during outages.
FAQs
When is a third-party library clearly worth it?
When stdlib lacks the capability (ORM relation graphs, advanced validation DSLs, vendor SDKs) or when organizational standards mandate a shared wrapper.
Record the gap in README or ADR text.
Should every package migrate from log to slog immediately?
Migrate at service boundaries starting with main.
Libraries should accept *slog.Logger parameters rather than forcing global defaults.
How do I enforce stdlib-first in CI?
Use import linters (depguard, custom go vet analyzers) to block duplicate HTTP frameworks in the same module without an exemption file.
What is the modern randomness migration path?
New code uses math/rand/v2 with explicit sources.
Legacy math/rand global functions remain for compatibility but avoid them in libraries.
How do stdlib best practices interact with chi/gin/echo?
Routers are fine when they export http.Handler and justify ergonomics.
Still apply stdlib timeout, client, and context rules underneath.
Should sqlite pools use MaxOpenConns(1)?
Often yes for embedded sqlite writers.
Server databases like PostgreSQL need higher pools tuned to instance limits.
How often should we revisit this list?
After each Go minor release and during annual dependency audits.
New stdlib packages may close gaps that previously required modules.
Are embed and slog considered stdlib-first wins?
Yes.
embed replaces asset-copy build steps; slog replaces ad-hoc JSON log formatters for many services.
What about testing-only third-party helpers?
testing and httptest cover most needs.
Add testify or similar only when table assertions materially improve readability and team consensus exists.
How do I document exceptions?
Keep an EXCEPTIONS.md or inline ADR links in go.mod comments listing approved non-stdlib packages and the capability gap filled.
Does stdlib-first conflict with gRPC?
No.
google.golang.org/grpc fills a protocol stdlib does not ship; still use stdlib context, tls, and logging patterns alongside it.
What is the biggest stdlib mistake in new codebases?
Importing a full web framework before exhausting net/http, slog, and database/sql defaults.
Start minimal; add layers when pain is measured, not anticipated.
Related
- The Go Standard Library: Batteries Included - Philosophy behind these rules
- Standard Library Basics - Runnable starting points
- os, io, bufio & path/filepath - I/O tier details
- database/sql & net Package Family Overview - Pool and HTTP defaults
- net/http Best Practices - HTTP-specific checklist
- Packages & Modules: Go's Unit of Distribution - Module hygiene
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).