How do staticcheck and golangci-lint differ from go vet? How would you set up linting for a team?
Question 432MediumGo 1.22 to 1.25
go vet is conservative and ships with the toolchain. staticcheck (Dominik Honnef) covers much more, with about 150 checks in several families:
SA: bugs, e.g. SA4006 (value never used), SA1012 (nil context), SA5007 (infinite recursion).S: simplifications.ST: style.QF: quick fixes.U1000: unused code.
golangci-lint is a meta-runner. It runs vet, staticcheck, errcheck, gosec, revive, ineffassign, bodyclose, sqlclosecheck and many more in parallel, shares one parsed and type-checked program between them, caches results, and supports --new-from-rev to lint only new code in legacy repos.
# .golangci.yml (v2 format)
version: "2"
linters:
default: standard
enable: [errcheck, gosec, bodyclose, errorlint, revive, gocritic]
formatters:
enable: [gofumpt, goimports]
# CI
go vet ./...
go tool golangci-lint run --new-from-rev=origin/main ./...
govulncheck ./...
Team advice interviewers want:
- Pin the linter version (tool directive or a Docker image), because new versions add findings.
- Start from a small, high-signal set.
- Require
//nolint:name // reasonwith a justification. - Run the same config in the editor (golangci-lint editor integrations) and enable staticcheck in gopls (
"staticcheck": true). - Treat gofmt/goimports as non-negotiable.
More on Modules, Packages & Tooling
- Q430What build metadata does a Go binary carry, and how do you make builds reproducible?
- Q431What does go vet catch? What does it report for the code below?
- Q433How does //go:embed work? Show the three target types.
- Q434What are the gotchas of go:embed patterns? Why is my .env or _redirects file missing?
- Q435What does this print? Explain Go's package initialization order.
- Q436Go forbids import cycles. How do you detect and break them?