All chapters in this track
- 01Chapter 1: Why Coding Standards Matter: Readability, Onboarding, Defects, Consistency at Scale
- 02Chapter 2: Naming Conventions
- 03Chapter 3: Formatting and Style
- 04Chapter 4: Comments and Self-Documenting Code
- 05Chapter 5: Error Handling Conventions
- 06Chapter 6: Code and Project Organization (Files, Modules, Packages)
- 07Chapter 7: Writing Testable Code
- 08Chapter 8: Commit Hygiene: Atomic Commits, Messages, PR-Sized Changes
- 09Chapter 9: Language-Specific Idioms and Anti-Patterns Across TypeScript and Python
- 10Chapter 10: Security-Conscious Basics: Input Validation, Secrets Handling, Injection Risks
- 11Chapter 11: Enforcing Standards Automatically: Linters, Formatters, CI Gates
- 12Chapter 12: Creating, Documenting, and Evolving a Team Style Guide (Exceptions and Legacy Code)
- 13Chapter 13: Worked Example: Refactoring a Messy Function to Standard
- 14Chapter 14: Worked Example: Drafting a Style Guide Section From Scratch
- 15Chapter 15: Common Interview Questions About Coding Standards and Code Quality
Chapter 4 of 15
Chapter 4: Comments and Self-Documenting Code
Why this matters
"Comment your code" is common advice that, taken uncritically, produces codebases full of comments that restate what the code already says — adding maintenance burden (a comment can silently go stale while the code it describes changes) without adding real information. The actual skill isn't "write more comments" or "write fewer comments" — it's knowing precisely which comments carry information the code itself cannot express, and eliminating every other kind.
The core concept: comments should explain WHY, not WHAT
[BEGINNER] Well-named code (Chapter 2) already explains what it does. A comment that restates this adds nothing:
// BAD: restates what the code already says clearly
// increment the counter by one
counter += 1;
// BAD: the function name and parameter names already say this
// validates the user's email address
function validateEmail(email: string): boolean { ... }
A comment earns its place specifically when it conveys something the code cannot express on its own — the reasoning behind a non-obvious decision, a constraint from outside the code itself, or a warning about a subtlety that isn't visible just by reading the logic:
// GOOD: explains WHY, a piece of information the code alone can't convey
// We retry exactly 3 times because the payment gateway's SLA guarantees
// resolution within 3 attempts; a 4th retry would exceed our checkout
// timeout budget and degrade the user experience for no additional benefit.
const MAX_PAYMENT_RETRIES = 3;
// GOOD: warns about a non-obvious constraint that isn't visible from the code itself
// This must run BEFORE the cache is populated, or a race condition can serve
// stale data to the first request after a deploy. See incident INC-4521.
initializeCache();
# BAD: restates the code
# loop through all users
for user in users:
...
# GOOD: explains a non-obvious constraint
# Must process users in ID order -- the downstream billing system assumes
# monotonically increasing IDs within a single batch, or it silently
# drops out-of-order records (see vendor docs, section 4.2).
for user in sorted(users, key=lambda u: u.id):
...
When a comment signals a missed refactoring opportunity
[SENIOR INSIGHT] A specific, common comment anti-pattern: a comment that explains a confusing block of code, rather than the code being refactored to no longer need the explanation. This is a missed opportunity, not a job well done — comments should supplement clear code, not compensate for unclear code.
Before — a comment compensating for unclear code:
// check if the user is eligible for the discount: must be a premium member,
// account older than 1 year, and have made at least 3 purchases
if (user.tier === "premium" && (Date.now() - user.createdAt) > 31536000000 && user.purchaseCount >= 3) {
applyDiscount(user);
}
After — the code refactored so the comment becomes unnecessary:
function isEligibleForLoyaltyDiscount(user: User): boolean {
const ONE_YEAR_MS = 365 * 24 * 60 * 60 * 1000;
const isLongTimeMember = (Date.now() - user.createdAt) > ONE_YEAR_MS;
const hasMinimumPurchases = user.purchaseCount >= 3;
return user.tier === "premium" && isLongTimeMember && hasMinimumPurchases;
}
if (isEligibleForLoyaltyDiscount(user)) {
applyDiscount(user);
}
The information from the original comment hasn't been lost — it's been encoded directly into names (isEligibleForLoyaltyDiscount, isLongTimeMember, hasMinimumPurchases, ONE_YEAR_MS), where it can never go stale relative to the logic, because it is the logic's own structure now, rather than a separate, parallel description of it that has to be kept manually in sync.
The staleness problem: why bad comments are worse than no comments
[IN PRACTICE] A comment describing what code does is a second, independent description of the logic — and unlike the code itself, nothing forces it to stay accurate when the code changes. A stale comment is actively worse than no comment at all, because it confidently asserts something false, and a reader has no way to detect the mismatch without already understanding the code well enough not to need the comment in the first place.
# Returns the user's full name, formatted as "Last, First"
def get_display_name(user):
return f"{user.first_name} {user.last_name}" # someone changed the format
# and forgot to update the comment above -- now it actively lies
This is exactly why comments explaining why (a decision, a constraint, a non-obvious rationale) are safer long-term than comments explaining what — a "why" comment describes something that doesn't automatically change just because the code's mechanics are refactored, while a "what" comment is duplicated information that can silently drift out of sync with every code change.
Docstrings and API documentation: a legitimate, distinct category
[BEGINNER] Comments explaining internal implementation details are different from docstrings/API documentation describing a function's public contract (parameters, return value, exceptions raised) for consumers who won't read the implementation at all — this category is legitimate and valuable even when it "restates" behavior, because its audience is specifically people who aren't looking at the code itself:
def calculate_shipping_cost(weight_kg: float, destination_country: str) -> int:
"""
Calculate shipping cost in cents for a package.
Args:
weight_kg: Package weight in kilograms. Must be positive.
destination_country: ISO 3166-1 alpha-2 country code.
Returns:
Shipping cost in cents.
Raises:
ValueError: If weight_kg is non-positive or destination_country is invalid.
"""
This isn't a violation of "comments should explain why, not what" — a docstring serves a genuinely different audience (API consumers, often working from generated documentation with no access to the implementation) than an inline comment does (someone actively reading this exact code), and its value proposition is correspondingly different.
Common mistakes
- Writing a comment that restates what well-named code already says, adding maintenance burden without adding information.
- Using a comment to compensate for confusing code, rather than refactoring the code (extracting named variables/functions) so the comment becomes unnecessary.
- Letting a "what"-focused comment go stale after a code change, actively misleading future readers — this specific failure mode is exactly why "why"-focused comments (rationale, constraints) are the safer default, since they don't automatically become false just because the code's mechanics changed.
- Treating docstrings/API documentation as violating "avoid restating what the code does" — they serve a genuinely different audience (API consumers without implementation access) and are a legitimate, distinct category from inline "what" comments.
Checklist: writing comments that earn their place
- Before writing a comment, ask "could a better name make this comment unnecessary?" If yes, rename/refactor instead of commenting.
- Ask "does this comment explain WHY, or just restate WHAT?" Keep the former; delete the latter.
- When you must comment on WHAT confusing code does, treat it as a signal to refactor, not a permanent solution — extract the confusing logic into a well-named function/variable instead.
- Write docstrings for any function/API that will be consumed without its implementation being read — this is a distinct, legitimate category from inline implementation comments.
- When changing code with a nearby "what"-focused comment, update or remove the comment in the same change — treat comment staleness as a bug, not a minor oversight.
Discussion / interview questions
-
"What's the difference between a good comment and a bad one?" Strong answer: A good comment explains WHY — a decision's rationale, an external constraint, a non-obvious warning — information the code itself cannot express; a bad comment restates WHAT the code does, which well-named code already communicates, adding maintenance burden (staleness risk) without adding real information.
-
"You find a complex, confusing block of code with a long comment explaining it. What would you consider doing?" Strong answer: Consider refactoring the code (extracting named variables/functions) so the comment becomes unnecessary — the information in the comment gets encoded directly into names, which can't go stale relative to the logic the way a separate, parallel comment description can.
-
"Why can a stale comment be worse than no comment at all?" Strong answer: A comment describing what code does is an independent, unenforced description that nothing keeps in sync with the actual code — when it goes stale, it confidently asserts something false, and a reader has no way to detect the mismatch without already understanding the code well enough not to need the comment.
-
"Is writing a docstring for a public function a violation of 'don't restate what the code does'?" Strong answer: No — a docstring serves API consumers who won't read the implementation at all, a genuinely different audience than someone reading the code directly; its value proposition (documenting a contract for external consumers) is distinct from an inline comment restating logic for someone already looking at that logic.
Chapter summary / cheat sheet
- Comments should explain WHY (rationale, external constraints, non-obvious warnings) — information the code can't express on its own — not WHAT the code does, which well-named code already communicates.
- A comment compensating for confusing code is a signal to refactor (extract named variables/functions), not a permanent fix.
- A stale "what"-focused comment is worse than no comment, since it confidently misleads readers with no way to detect the mismatch — "why"-focused comments are safer long-term, since they don't automatically become false when code mechanics change.
- Docstrings/API documentation are a legitimate, distinct category serving consumers without implementation access — not a violation of the "explain why, not what" principle.