Skip to main content

The problem

Documentation serves two masters: the engineer who writes it and the engineer who reads it six months later. Too little documentation leaves readers guessing. Too much buries the signal in noise. The goal is documentation that helps engineers understand and use code correctly. Nothing more, nothing less. The principles in this guide apply to all languages. The examples are in Go since that is most of the backend, but the philosophy is universal.

Quick checklist

Before submitting documentation, verify each item. Accuracy
  • Every claim matches actual code behavior
  • Return values match what code returns
  • Error conditions listed are possible and described correctly
  • Default values match actual defaults
  • Constraints documented are enforced, and the docs note when and how
Completeness
  • Every symbol has a doc comment
  • Package has a doc.go if it has non-trivial behavior
  • Non-obvious behavior is documented (edge cases, nil handling, concurrency)
  • The why is explained for design choices that are not self-evident
  • Every named SQL query has a doc comment block
Quality
  • Doc comments start with the symbol name
  • Uses prose, not bullet lists, unless items are parallel
  • Depth matches complexity
  • SQL comments add non-obvious context instead of restating obvious clauses
  • Cross-references use bracket syntax: [TypeName], [FuncName]
  • No stale documentation from copy-paste or refactoring
Verification
  • You read the implementation, not just the signature
  • For value plus error returns, you checked what value returns on failure
  • For unmarshal operations, you verified whether partial values return
  • Examples compile and run
  • SQL comment examples match real query behavior (ordering, fallback, joins)

Writing style

Write naturally. Use prose for explanations, not bullet points. Bullet lists are for parallel items or steps. A list of single sentence bullets is often better as a paragraph.

Document the why, not the what

The code shows what it does. Documentation should explain why it exists, why it works this way, and what could go wrong.

Documenting design choices

When you choose between reasonable alternatives, explain the reasoning in a sentence.

Public API documentation

Every exported function, type, constant, and variable must be documented. This is the contract with users of the code. Unexported functions and methods must be documented too. They have no external contract, but the next reader still needs to know what they do and why they exist without reverse-engineering the body. A single sentence is usually enough. The depth of documentation should match complexity. A simple getter needs one line. A distributed algorithm needs paragraphs.

Simple functions

Complex functions

When to include specific details

Parameters: Document when the purpose is not obvious from the name and type, or when there are constraints like must be positive. Return values: Explain when return patterns are subtle or when multiple success states exist. For functions that return a value plus an error, document what value returns on failure. Error conditions: List specific errors only when callers need to handle them differently. Concurrency: Document when a function or type is safe or unsafe for concurrent use. Performance: Mention non-obvious characteristics that affect usage decisions. Context: Document context behavior only if it is non-standard.

SQL query documentation

Named SQL queries are part of the public contract between application code and the database. Treat query comments like API documentation. For SQL query docs, explain why this query exists, how it resolves non-obvious behavior, and what guarantees callers can rely on. Do not just restate the SELECT clause. Keep SQL comments concise. In most cases, two to five lines are enough. If a comment is longer than the query, each sentence must carry non-obvious information such as fallback guarantees, deterministic ordering, intentional join behavior, or performance tradeoffs. Avoid duplicate explanations. If the SQL already makes behavior obvious, for example AND s.health = 'healthy', do not repeat that in prose unless you are documenting a non-obvious guarantee that depends on it. For selection queries with fallback logic, document deterministic behavior explicitly. If exact matching must win over wildcard matching, explain how SQL enforces it, for example with ORDER BY and not candidate list order. For query docs that are not obvious from a quick read, include a small concrete example with inputs and the expected returned row. This is required for logic that depends on ordering, fallback, or tie-breaking. Document performance-sensitive choices when they are intentional, for example using LIMIT 1 to avoid transferring large payload rows that are not selected.

What not to document

Do not document implementation details in doc comments. Those belong inside the function. Do not explain that context is used for cancellation or mention O(1) performance unless it is surprising.

Package documentation

Every significant package should have a doc.go file with the package comment and declaration. It should explain what the package does, why it exists, how it fits into the system, key concepts, usage, and cross-references.

Structure of doc.go

Use # headers to organize sections. Include a usage example.

Internal code

Internal functions have different documentation needs. The audience is teammates maintaining this code. The why matters even more than the what.

Complex algorithm documentation

For complex internal logic, explain the approach and reasoning.

Types and interfaces

Type documentation should explain what the type represents and any constraints or invariants. Interface documentation should focus on the contract and concurrency guarantees.

Error documentation

Document sentinel errors with meaning and conditions.

Constants and variables

Document purpose and reasoning when it is not obvious.

Examples

Use Go example tests for non-trivial usage patterns. Examples compile and run, so they do not go stale.

Test documentation

Document test helpers and complex test scenarios so future maintainers understand the purpose.

Document what not to do

Warn against common mistakes when a misuse would be easy and costly.

Verify before you document

The most dangerous documentation is confident and wrong. Read the implementation. Document what the code does, not what you think it should do. Common verification failures include return values on error, partial values from unmarshal, constraint enforcement timing, defaults, and context behavior.

Common mistakes

Restating the signature adds no value. Documenting irrelevant details creates noise. Missing critical information is dangerous. Stale documentation is worse than no documentation.

Go conventions

Start doc comments with the name of the thing being documented. Use present tense. Write complete sentences. Use bracket syntax for references, for example [TypeName] and [FuncName].

Deprecation

When deprecating an API, provide a migration path.

Keeping documentation alive

Update documentation whenever you change behavior, parameters, or error conditions. When reviewing code, check that documentation still matches implementation.