Code documentation has two failure modes: too little (the next reader has no chance) and too much (comments decay into lies as code changes around them). The balanced approach is principle-driven: document the things that the code itself cannot say.
This page is about which comments earn their place, which decay into noise, and the practices that produce documentation that survives.
Code says what is happening. Comments should say why.
Good comments answer: why this approach? Why this constraint? What would surprise a reader? What is the non-obvious invariant?
Bad comments answer: what does this code do? (The code already shows that.)
A worked example:
// Loop through orders and find total amount.
for order in orders:
total += order.amount
The comment restates the code. It is noise.
// Use atomic increment because this can be called from multiple threads;
// see incident #1234 for what happens with non-atomic.
counter.atomic_add(1)
The comment explains the why — a constraint not visible in the code. It earns its place.
"This list must never be modified after construction" is a real constraint. The code may not visibly enforce it; the comment makes it visible to future readers.
"This format must match the schema in /docs/api-spec.json" links to information not in the codebase.
"Workaround for bug in library X version Y; remove when we upgrade past Z." This is the kind of comment that prevents another engineer from "improving" the workaround away without realizing what they're undoing.
"Using map instead of dict for ordering guarantees" — explains a non-default choice.
"Sorting before filtering — counterintuitive but X% faster on the typical input." Without the comment, a future reader will refactor "for clarity" and lose the perf.
"This API is being replaced; new code should use Y." Helps the next reader avoid extending the deprecated path.
"Increment counter by 1" before counter += 1. The code shows it; the comment is duplication.
Banners and section headers in comments. Often a sign the function is too long; sub-functions would be a better answer than visual division.
Version control already tracks this. Inline change history is noise that decays as the codebase evolves.
"Will eventually do X." If it does not yet do X, do not pretend it does. Either implement X, or remove the comment, or note honestly that X is not implemented.
"This elegant solution efficiently handles the use case." Subjective claims are not documentation.
The format depends on the language and tooling.
JavaDoc is appropriate for public APIs. Document the contract — what the function does, the parameter meaning, the return semantics, the exception types. Skip the obvious; document the non-obvious.
/**
* Validates an order and returns true if it can be processed.
*
* <p>The validation includes credit check (calls credit service), inventory
* availability (calls inventory service), and shipping address verification.
* Returns false on any failure; the specific cause is logged.
*
* @param order The order to validate. Must not be null.
* @return true if the order can be processed, false otherwise.
* @throws CreditServiceException If the credit service is unreachable.
*/
Similar role to JavaDoc. PEP 257 covers conventions. The principle: document the contract, not the implementation.
Most useful for the why-not-what cases described above. Should be short, specific, and tied to a specific line.
The top of a file or module is the right place for context that applies to everything in the file: what this module does, what it depends on, what callers of this module need to know.
Some things belong in code; some belong outside.
The choice depends on audience. A developer setting up the project for the first time benefits from a README. A developer modifying a specific function benefits from comments at that function.
Documentation decays. Code changes; comments stay. Eventually the comment is wrong, often more harmful than no comment at all (the reader trusts the comment, which describes obsolete behavior).
Strategies that combat decay:
Short documents that capture a single decision: context, decision, consequences. Stored in the repository. Useful for "why did we do it this way?" questions years later. See TechnicalLeadershipSkills for the broader practice.
Worked input/output examples are dramatically more useful than parameter descriptions for most APIs. A user can match an example to their case faster than they can synthesize from a contract description.
Operational documentation: "what to do when X happens." Live alongside code that produces specific errors or alerts. See RunbookAutomation.
Documentation of incidents: what happened, why, how it was resolved, what would prevent recurrence. Specifically not blaming individuals; specifically identifying systemic causes.