skip to content

A Go library's doc comment shows a usage snippet that no longer compiles. How do you stop that drift?

level: seniorimportance: should knowfreq 38%

answer

  1. the comment's code is the only unchecked code
  2. make the example a real caller
  3. compiled with the package's tests
  4. a trailing comment turns it into an assertion
  5. write it in the external test package

basics

~20 s

Move the snippet out of the comment into a runnable Example function in a _test.go file. It is compiled with the package, so an API change breaks the build, and a // Output: comment makes go test check what it prints.

solid answer

~50 s

Text inside a doc comment is never compiled, so a usage sketch is a promise nothing verifies — it rots the first time a signature changes and the maintainer finds out from a user's issue. The fix is to turn it into a runnable `Example` function in a `_test.go` file. Because it is real code in the package's test build, a changed signature now fails the build like any other caller. Adding a `// Output:` comment as the last thing in the function goes further: `go test` runs the example, captures what it printed, and fails if it does not match, so the documented result is an assertion too. Put the example in the external `package foo_test` so it can only use the exported API, exactly as a user would, and let CI run it. What remains in the doc comment is prose plus a pointer, not code.

code

go · 7 lines
go
// LoadConfig reads the operator configuration from the named file.
//
//	f, err := os.Open("console.conf")
//	if err != nil {
//		return err
//	}
//	defer f.Close()

go deeper

for a junior

Know that code written inside a comment is never compiled, and that Go lets you write an Example function in a _test.go file instead so the snippet is real code.

for a middle

Separate the two guarantees cleanly: being in the test build gives you compilation, and a trailing // Output: comment is what makes the printed result checked. State that an example without one is compiled but not run.

for a senior

Bring the operational judgment: external test package so the example uses only exported API, a deliberate decision about nondeterministic output rather than a pasted run, and reliance on the pipeline that already runs so no new process is needed.

for a principal

Argue it as a maintenance-cost decision for a library other teams depend on — verified examples move the cost of doc drift onto the change that caused it, and set the expectation that a public entry point ships with one.

## The failure you are fixing A doc comment is text. Every convention around it — start with the identifier, indent code blocks, link with brackets — is about how it is *rendered*, never about whether it is *true*. Indented lines inside a comment look like Go, are highlighted like Go and are read like Go, but no compiler ever sees them. So the sketch that was correct when it was written survives every rename, every added parameter, every changed return signature, and stays on the package's front page saying something false. The person who discovers it is a stranger, in an issue titled "the example in the docs doesn't work", and by then it has been wrong for months. That asymmetry is the whole argument: the code in a comment is the only code in the repository that nothing checks. ## Example functions Go's answer is that documentation examples are *code*. An example is a function named with the `Example` prefix, living in a `_test.go` file alongside the package: ```go func ExampleTrimSpace() { fmt.Println(strings.TrimSpace("\t hello \n")) // Output: hello } ``` Two separate guarantees come out of this, and it is worth being precise about which one you get from what: 1. **It compiles.** The file is part of the package's test build, so the example is a real caller of the API. Rename the function, add a parameter, change a return type, and the build breaks — locally, and in CI, in the same run as everything else. This guarantee is free and you get it whether or not you assert anything. 2. **Its output is checked.** If the last thing in the function body is a comment of the form `// Output:` followed by the expected text, the example is executed, its standard output is captured, and it is compared against that text after trimming leading and trailing space. A mismatch is a test failure. Now the documented *result* is verified, not just the documented *call*. The corollary matters when you are advising a team: an example with no `// Output:` comment is compiled but not run. That is still the larger half of the value — it catches signature drift, which is what actually breaks users — but it does not catch behaviour drift, so do not describe such an example as verified. ## Write it as a user, not as an insider A `_test.go` file may declare `package foo` or `package foo_test`. For examples, prefer the external form: ```go package console_test ``` Inside it you can only reach the exported surface, so the example is forced to demonstrate what a user can actually do. An example in the internal package can quietly reach for an unexported constructor, and then it documents a path nobody outside can take — and it keeps compiling after you accidentally unexport something, so it stops protecting the thing you wanted protected. The external form also sidesteps import cycles when the example needs a package that imports yours. ## What to do about output you cannot pin Some results are genuinely not stable: a timestamp, an address, anything derived from map iteration, anything concurrent. Do not paper over it by asserting one observed run — that produces a flaky test and teaches people to ignore it. The options, in order of preference: restructure the example to print something derived and deterministic (a count, a sorted slice, a boolean); print a fixed value that the interesting call merely feeds; or drop the `// Output:` comment and accept a compile-only example. Choosing deliberately between those is the judgment part of this question. ## What stays in the doc comment Not nothing. The prose still has to say what the thing does and what the caller must do — and it should point at the example, which documentation tooling renders next to the symbol so the two arrive together. A one- or two-line indented sketch in a comment is still fine where it is illustrative rather than load-bearing; the smaller it is, the less surface it has to rot. What you are removing is the twenty-line "here is how you use this package" block that is the single most likely thing in the repository to be wrong. ## Making it stick The reason this works organisationally is that it needs no new process. Examples run under `go test ./...`, which CI already does, so a rotted example fails the same pipeline as a rotted unit test and is fixed by the person who broke it, in the change that broke it. Compare with a doc-comment snippet, whose maintenance depends on someone remembering to reread prose while changing a signature — which is exactly the discipline that failed and produced the issue you are answering.

  • What does an Example function with no // Output: comment still give you?
    Compilation. It is built as part of the package's test build, so it is a real caller: rename a function, change a signature, and the example fails to build. It just is not executed, so no behaviour is verified. That is the right choice when the result genuinely cannot be pinned down, and it should be described as compile-checked, not verified.
  • Why put the example in package foo_test rather than package foo?
    Because it then sees only the exported surface and therefore documents what a user can actually do. An internal example can reach an unexported helper, showing a path no importer can follow, and it keeps compiling after something is unexported — losing exactly the protection you wanted. The external form also avoids import cycles when the example needs a package that imports yours.
  • The value your example prints includes a timestamp. How do you keep it verifiable?
    Do not assert an observed run. Restructure so the printed value is derived and deterministic — a duration compared to a threshold, a formatted fixed instant, a count — or feed the call a fixed input and print something stable. If nothing works, drop the output comment and keep it as a compile-only example rather than shipping a flaky check.
  • Does this mean no code should ever appear inside a doc comment?
    No. A short indented sketch is fine when it is illustrative — a one-line call shape, a config fragment — because small text has little to rot. What moves out is the load-bearing usage walkthrough, the block a new user copies. Keep the prose, point it at the example, and let the tooling render both beside the symbol.

A snippet in a comment is a photograph of the API; an example function is a live mirror. Only one of them changes when the thing in front of it does.

saying these in an interview costs you the question

  • Assumes code inside a doc comment is checked by some tool
  • Thinks every Example function runs even without an output comment
  • Pastes one observed run into // Output: for a nondeterministic value
  • Writes examples in the internal package and reaches unexported helpers
  • Proposes a review checklist instead of a check the build performs
  • Believes gofmt or go vet validates snippets inside comments