Go

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 // reason with 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

All 36 Modules, Packages & Tooling questions