The Style Argument Go Deleted
Every other language holds a formatting referendum: spaces or tabs, brace style, where the commas go. Teams write style guides, reviewers request re-indentation, and merges conflict over whitespace. Go's answer was to hand the question to a program.
gofmt reads any Go file and emits the one canonical layout — tabs for indentation, one statement per line, declarations spaced by the standard rules, comments realigned to what they document. It has no configuration: no style flags, no line-length limit (Go code routinely runs past 100 columns because the formatter refuses to break lines for width alone), and no opinions about your naming. What it has instead is three modes:
gofmt -l . # list files whose formatting differs — print, change nothing
gofmt -d main.go # show the diff it would apply
gofmt -w . # rewrite files in place
The workflow these support is the whole point. During development, -l answers "is anything off?" with a file list, -d answers "what exactly?" with a reviewable diff, and -w answers "fix it." go fmt ./... is the package-level wrapper that runs gofmt -l -w across a pattern and prints the files it touched — convenient locally, wrong in CI, because CI should check rather than mutate: a job that reformats silently hides the change from review. The durable check is one line:
test -z "$(gofmt -l .)" # exits non-zero if any file is unformatted
Wire that into the same pipeline that runs the tests, and formatting debates end permanently. Enable format-on-save in your editor and the check never fires at all.
Two clarifications prevent the usual disappointments. First, gofmt is not a linter — it will happily format buggy code into perfect alignment, which is why it sits alongside go vet below rather than replacing it. Second, it does not strip things you find redundant, redundant parentheses around an if condition included: the compiler accepts them and gofmt leaves them, while the convention across every Go codebase writes if ready { with none. For import management — sorting groups, adding missing imports, deleting unused ones — the companion tool is goimports, covered in Imports, goimports, and go mod tidy. The habit to install today is the one that scales: save → formatted, commit → formatted, CI → formatted is a property, not a review comment.
go doc: The Manual That Cannot Go Stale
You are reading a function you have never used and want its contract — the parameters, the behavior, the failure cases — without leaving the terminal or opening a browser tab on possibly-the-wrong-version docs. Go ships the answer inside the toolchain:
go doc # package docs for the current directory
go doc fmt # overview of package fmt
go doc fmt.Println # one symbol: signature + doc comment
go doc -all net/http # every documented symbol in the package
go doc -src fmt.Println # the actual source of the declaration
go doc -u cache # include unexported symbols too
The crucial fact about go doc is where its content comes from: it renders the doc comments attached to declarations in the source in front of you. There is no second copy of the documentation to update and no generated file to commit — the comment above func Parse in the standard library or in your own package is the manual. That closes a loop with Comments and Doc Comments: write the comment in the right place with the right first sentence, and every surface — terminal, editor hover, pkg.go.dev — inherits it. Write a vague one and go doc will faithfully render the vagueness.
Reading the flags as a ladder: the default shows a symbol's documentation plus a one-line summary of what sits beside it; -all widens to the full package listing; -src jumps from prose to implementation when the comment raises a question the code answers faster; -u opens up the internals when you are debugging inside the package rather than consuming it. One behavior trips up newcomers: in a package main program, go doc shows the package comment but hides the exported symbols unless you pass -cmd — the assumption being that a command's API is its command line, not its functions.
This makes the standard library readable at the depth you need, offline, at the exact version you are compiling against — a version-sensitive detail browser docs get wrong the moment your toolchain and the website diverge. Get in the habit of resolving API questions with go doc before writing an import: it takes two seconds and it is always right about your build.
go vet: Catching What the Compiler Was Built to Allow
The compiler's job is to prove your program is well-formed Go. It is deliberately blind to questions like "did you mean the verb you wrote?" — those belong to go vet, whose charter is printed by go help vet: report likely mistakes in packages. It runs analyses over your compiled code and flags patterns that are legal and almost certainly wrong.
The printf analysis is the famous one. This compiles without complaint:
fmt.Printf("user %d spent %s\n", name, amount) // name is a string
Printf accepts ...any, so the type mismatch passes straight through the compiler — and surfaces at runtime as %!d(string=alice). go vet matches each verb against the corresponding argument's type and reports it before the program ever runs:
./report.go:14:2: Printf format %d has arg name of wrong type string
The analyses are worth knowing by name because they cluster around specific bug classes:
| Analysis | Catches | Example |
|---|---|---|
printf |
verb/type mismatches, bad Printf wrappers | %d fed a string |
stringintconv |
string(int) producing one rune, not digits |
string(65) where "65" was meant |
structtag |
malformed or mismatched struct tags | json:"id" with a missing quote |
copylocks |
copying a value containing a sync.Mutex |
passing a struct by value that locks |
unreachable |
statements after return |
dead branch that never runs |
Each one is code that compiles, passes casual reading, and fails in production — exactly the gap between "valid Go" and "correct Go" that this tool exists to cover. Run it the way you run the compiler:
go vet ./...
Then note the integration you get for free: go test runs a vet subset — printf, stringintconv, atomic, and friends — automatically before executing tests. A test file with a broken format verb fails the test run without any CI configuration, which is why a green go test already means slightly more than you assumed.
The full ladder, stated plainly: the compiler proves the program is well-formed; go vet reports likely mistakes; external linters (staticcheck, golangci-lint) extend the same pattern with style and correctness checks beyond the standard set. Nothing here overlaps arbitrarily — vet deliberately stops where analysis gets expensive or opinionated. Keep gofmt -l and go vet ./... as two commands in the same CI step and you have the same gate a Go team uses in production. With tooling habits set, the rest of this roadmap is language: starting with the expressions everything else computes.
Quiz
1. CI runs `gofmt -l .` and it prints two file names. What does that mean, and what should CI do next?
2. `fmt.Printf("count: %d\n", label)` compiles, where label is a string. What happens at build time, and which tool objects?
3. Where does the text `go doc fmt.Println` prints come from?
4. You run only `go test ./...` in CI with no explicit vet step. Which mistakes can still be caught automatically?
Flashcards
Question
What do `gofmt -l`, `gofmt -d`, and `gofmt -w` each do, and which belongs in CI?
Click to reveal answer
Answer
-l lists files whose formatting differs from canonical (silent otherwise), -d prints the diff it would apply, -w rewrites files in place. CI should use the check form — fail when -l outputs anything — because letting CI rewrite files moves unreviewed changes into the commit; -w belongs in the developer's editor on save.
Question
Name three mistakes go vet reports that the Go compiler deliberately accepts.
Click to reveal answer
Answer
printf verb/type mismatches (%d with a string argument — legal because Printf takes ...any), stringintconv's string(int) producing a single rune instead of digits, and copylocks: copying a value that contains a sync.Mutex. Also unreachable code after return and malformed struct tags. All compile cleanly and all are likely bugs.
Question
What is the source of truth behind `go doc` output, and when is that an advantage over a docs website?
Click to reveal answer
Answer
The doc comments attached to declarations in your local source. It is version-exact: the docs you read are the docs of the code your toolchain compiles, offline, including your own packages — websites show the latest published version, which may not match a pinned dependency.
Question
Does gofmt enforce style rules like line length or flag dead code? What is its scope?
Click to reveal answer
Answer
No. gofmt only canonicalizes layout — indentation (tabs), spacing, line breaks, comment alignment — with zero configuration and no line-length limit. It will happily format buggy code perfectly. Dead code and likely-mistake detection are go vet's job; naming and style rules beyond layout belong to external linters.
Question
What happens when you run `go test` with no vet configuration at all?
Click to reveal answer
Answer
A subset of go vet's analyses runs automatically while building the test binary — printf, stringintconv, atomic and related checks — so mismatches like a %d verb fed a string fail the test run by default. The complete analysis suite still requires an explicit `go vet ./...` in your pipeline.
Revision Notes
Key Takeaways
- 1. gofmt has no options and no line-length limit — canonical output means formatting stops being a review topic
- 2. Check in CI with -l, fix locally with -w; a CI job that rewrites files smuggles changes past review
- 3. go doc renders your doc comments at the exact version you compile, which is why comment placement is API work
- 4. go vet covers the gap between valid and correct: printf verbs, string(int), mutex copies, unreachable code — all compile fine
- 5. go test already runs a vet subset, so a green test run means more than most people assume
Interview Tips
- • "How do you keep formatting consistent across a team?" — gofmt in the editor on save plus a `test -z "$(gofmt -l .)"` CI gate; mention explicitly that CI checks rather than rewrites
- • "What's the difference between the compiler, go vet, and a linter?" — validity vs likely mistake vs style/maintainability, with printf as the concrete example only vet catches
- • "Give an example of code that compiles but is wrong" — Printf with %d and a string; explain ...any defeats compile-time checking and vet's printf analysis restores it
- • "How do you read unfamiliar standard library code?" — go doc for the contract (offline, version-exact), -src when the comment raises questions; note -cmd's behavior for package main
Cheat Sheet
Formatting and Inspection Tools
Command cheat sheet:
| Command | Answers | Side effect |
|---|---|---|
gofmt -l . |
which files are unformatted? | none (prints names) |
gofmt -d f.go |
what would change? | none (prints diff) |
gofmt -w f.go |
fix it | rewrites the file |
go fmt ./... |
fix the pattern | runs gofmt -l -w, prints touched files |
go doc [pkg|sym] |
what is the contract? | none |
go doc -all/-src/-u |
full listing / source / unexported | none |
go vet ./... |
likely mistakes | none (exit code) |
CI gate:
test -z "$(gofmt -l .)" || { echo "unformatted files"; exit 1; }
go vet ./...
go test ./... # vet subset (printf, stringintconv, ...) runs automatically
Division of labor:
| Tool | Question it answers |
|---|---|
| compiler | is this valid Go? |
| go vet | is this a likely mistake? |
| external linters | is this idiomatic / maintainable? |
vet high-yield checks: printf verbs vs arg types · string(int) runes · struct tag syntax · Mutex copies · unreachable code.
Continue Learning
Before You Start
- No prerequisites listed.