C++ • SOFTWARE CRAFT AND COMMUNICATION

Writing Comments — Write comments that explain intent and invariants

Transform your comments from restating code into preserving the reasoning future maintainers desperately need.

Historical Context & Motivation

The practice of embedding human-readable annotations within source code is nearly as old as programming itself. Early assembly languages allowed programmers to place remarks after a semicolon, and when FORTRAN introduced the 'C' column comment in 1957, it codified the idea that source files should speak to two audiences: the machine and the human. Yet for decades, the dominant advice was simply "comment your code," with little guidance on what to say. The result was a culture of paraphrasing the code—comments like // increment i that added noise rather than knowledge. As software systems grew in scale and lifespan, the industry began to recognize that the most valuable comments are those that capture intent (the "why") and invariants (the guarantees a piece of code promises to uphold).

1972
Kernighan & Plauger's Style
"The Elements of Programming Style" urged programmers to make code self-documenting, planting the seed that comments should not merely restate syntax but illuminate logic.
1986
Design by Contract
Bertrand Meyer introduced Design by Contract in Eiffel, formalizing the notion of preconditions, postconditions, and invariants—ideas that deeply influenced how C++ programmers think about commenting class guarantees.
1999
Extreme Programming & "Code Smells"
Kent Beck's XP community popularized the idea that redundant comments are a code smell, shifting the conversation toward selective, high-value commenting that explains decisions, not mechanics.
2008
Clean Code
Robert C. Martin's "Clean Code" codified a hierarchy of comment quality: legal comments, informative comments, explanations of intent, and warnings, placing intent-driven comments near the top.
2020s
C++20 Contracts & Modern Tooling
The C++ Contracts proposal (targeting C++26) aims to make preconditions and postconditions first-class language features, further underscoring that invariant documentation is not optional but architectural.

This progression reveals a central question: if the code already tells the machine what to do, what should the comment tell the human? The answer, distilled from five decades of practice, is that comments must capture the knowledge that cannot be recovered by reading the code alone—the reasoning behind a design choice, the constraints that must hold for correctness, and the traps that a future maintainer might otherwise fall into.

Core Principles of Intent & Invariant Comments

Effective comments in C++ rest on a few foundational principles. Understanding these principles lets you evaluate every comment you write against a clear standard rather than relying on vague intuition about whether something "looks documented enough." The overarching philosophy is that a comment's value is inversely proportional to how easily its content could be inferred from the surrounding code.

1

Intent Over Mechanics

Explain why a block of code exists and what goal it achieves, not what each line does. The compiler already interprets the "what."
2

State Your Invariants

Document the conditions that must remain true throughout the lifetime of an object or function. Invariants are the promises your code makes to its callers.
3

Comment the Non-Obvious

Reserve comments for subtle algorithmic choices, performance trade-offs, concurrency constraints, or domain knowledge that a competent C++ developer would not immediately recognize.
4

Keep Comments Close & Accurate

Place comments adjacent to the code they describe and update them whenever the code changes. A stale comment is worse than no comment—it is actively misleading.
5

Prefer Expressive Code First

Before writing a comment, ask whether renaming a variable, extracting a function, or introducing a type alias would make the comment unnecessary. Comments complement good naming—they do not substitute for it.
✦ KEY TAKEAWAY
Think of a comment as a letter to a future colleague who has full access to the code but none of the context from the design meeting, the debugging session, or the domain specification that shaped your decisions. If your comment merely narrates the syntax—like a tour guide pointing at a building and saying "that's a building"—it wastes everyone's time. If it instead explains why the building was placed here and what load it must bear, it becomes indispensable.

Visual Explanation: The Comment Quality Spectrum

Not all comments are created equal. The following diagram organizes comment types along a spectrum from harmful to highly valuable, mapping each category to an example so you can calibrate your own commenting habits. The key insight is that value increases as the comment moves further from what the code literally does and closer to what the programmer was thinking and guaranteeing.

The top row shows five comment categories arranged from harmful (left, red) to essential (right, green). The bottom flowchart provides a quick decision process: if the code already communicates the information, delete the comment; if the comment explains intent or an invariant, keep it; otherwise, refactor the code to make the comment unnecessary.

Notice that the two rightmost categories—intent and invariant—carry the highest value because they encode information that is structurally absent from the code itself. A variable name can hint at its role, but it cannot express why a particular algorithm was chosen over its alternatives, nor can it state a class-wide guarantee that must survive every future modification.

How Intent and Invariant Comments Work in Practice

Anatomy of an Intent Comment

An intent comment captures the rationale behind a design decision. It answers questions such as: why this algorithm instead of a simpler one? Why this particular constant? Why this ordering of operations? Consider the following C++ snippet without and then with an intent comment:

The left version forces the reader to reverse-engineer the scheduling strategy; the right version makes it explicit.
Without Intent CommentWith Intent Comment
std::sort(tasks.begin(), tasks.end(), [](auto& a, auto& b){ return a.deadline < b.deadline; });// Sort by earliest deadline first so the scheduler can greedily assign tasks and minimize the number of missed deadlines (EDF heuristic). std::sort(tasks.begin(), tasks.end(), [](auto& a, auto& b){ return a.deadline < b.deadline; });

Anatomy of an Invariant Comment

An invariant comment states a property that must hold at a particular program point or throughout the lifetime of an object. In the language of formal verification, an invariant is a predicate P such that if P holds before an operation, P also holds after. The three most common types in C++ codebases are class invariants (must hold between every public method call), loop invariants (must hold at the start of every iteration), and preconditions / postconditions on individual functions.

💡 Design by Contract in C++ Comments
Until C++ contracts become standardized, the idiomatic way to express them is through structured comments. Use prefixes like // PRE:, // POST:, and // INV: to make the contract visually scannable. Many teams back these with runtime assertions: assert(size_ <= capacity_); placed after the comment.

The Formal Invariant Preservation Rule

INVARIANT PRESERVATION
{ I ∧ guard } body { I }
Where I is the invariant predicate, guard is the loop or method entry condition, and body is the code block. This Hoare-logic triple states that if the invariant and the guard hold before the body executes, the invariant still holds after.

When you write an invariant comment, you are essentially providing this Hoare-logic triple in natural language so that future developers can verify—mentally or with tooling—that every code path preserves the stated guarantee. This is particularly critical in C++ where undefined behavior, manual memory management, and multithreaded access can silently violate invariants that higher-level languages enforce automatically.

Taxonomy of High-Value Comments in C++

Beyond the broad categories of intent and invariant, C++ codebases benefit from several specific comment patterns. The diagram below classifies these patterns and maps each to the code construct where it is most useful. Recognizing which pattern applies is the first step toward writing comments that are consistently helpful.

The tree organizes high-value comments into three families: intent (blue, covering algorithm choice and domain rationale), invariant (green, covering class and loop invariants), and warning (amber, covering performance traps and undefined-behavior risks). Pre/postconditions span both intent and invariant families. The bottom row shows recommended comment prefixes.

Adopting a small set of consistent prefixes such as // WHY:, // INV:, // PRE:, // POST:, and // WARN: makes your comments machine-greppable, visually distinct from ordinary prose, and easy for new team members to learn. Several C++ style guides, including Google's and LLVM's, endorse similar structured-comment conventions.

Worked Example: Commenting a Thread-Safe Ring Buffer

Consider a simplified lock-free ring buffer class used in an audio processing engine. This is exactly the kind of code where intent and invariant comments shine: the data structure is subtle, concurrency constraints are invisible in the syntax, and a naive future edit could introduce data races. We will walk through the process of adding comments step by step.

Commenting a RingBuffer Class
1
Step 1 — State the class invariant at the topBegin by documenting what must always be true about the object's state between public method calls. For our ring buffer: // INV: 0 <= (write_pos_ - read_pos_) <= capacity_. The buffer never overwrites unread data. write_pos_ and read_pos_ are monotonically increasing atomic<size_t> values; the actual index is obtained via modulo capacity_. This single comment tells every future reader the foundational guarantee the class maintains.
Class invariant documented at class scope
2
Step 2 — Add preconditions to push()Before the push() method body, specify the caller's obligation: // PRE: Only a single producer thread may call push(). Concurrent calls from multiple producers are undefined behavior. This is not something the type system enforces, so a comment (ideally paired with a debug assertion) is the only way to communicate it.
Precondition makes threading contract explicit
3
Step 3 — Explain the intent behind memory orderingInside push(), the line write_pos_.store(wp + 1, std::memory_order_release); demands an intent comment: // WHY release: Ensures the element written to buf_[wp % capacity_] is visible to the consumer before it sees the updated write_pos_. Paired with acquire load in pop(). Without this, a future developer might "optimize" to memory_order_relaxed and introduce a subtle data race.
Intent guards against dangerous "optimization"
4
Step 4 — Add a postcondition to pop()Document the guarantee that pop() provides: // POST: If pop() returns true, 'out' contains the oldest unread element and read_pos_ has advanced by 1. If false, the buffer was empty and 'out' is unchanged. This postcondition lets callers write correct consuming loops without reading the implementation.
Postcondition enables correct usage without reading implementation
5
Step 5 — Add a warning comment for the capacity constraintNear the constructor, note: // WARN: capacity must be a power of two. We use bitwise AND instead of modulo for performance in the audio thread. Passing a non-power-of-two value silently produces incorrect index wrapping. This warning captures a non-obvious constraint and explains the intent behind the power-of-two requirement simultaneously.
Warning + intent combined to prevent silent bugs

Strengths, Pitfalls, and Comparisons

Intent and invariant comments, like any tool, carry trade-offs. Understanding these trade-offs helps you deploy comments where they provide the highest return on investment and avoid the well-known anti-patterns that give commenting a bad reputation.

Strengths and pitfalls of intent/invariant commenting
AspectStrengthPitfall
Maintenance burdenIntent rarely changes even when implementation details do; "sort by deadline for EDF" survives a switch from std::sort to std::partial_sort.If a comment restates implementation, every refactor demands a comment update—and the update is often forgotten, creating a stale comment.
Onboarding speedNew team members can understand the architecture by reading invariant headers on key classes without tracing every code path.Over-commenting trivial code slows readers down by forcing them to process text that adds no information.
Bug preventionExplicit preconditions and invariants give code reviewers a checklist: does this change violate the stated guarantee?Comments cannot be compiled or tested; a stated invariant may diverge from reality if not backed by assertions or contract checks.
Concurrency safetyThreading contracts ("only one producer") prevent data races that static analysis often cannot detect.Without enforcement, a developer may ignore the comment under deadline pressure, leading to race conditions.
Code review qualityReviewers can evaluate design decisions (intent) and verify invariant preservation without re-deriving them.Teams that treat "add comments" as a blanket review feedback item may incentivize low-value redundant comments.
✦ KEY TAKEAWAY
Intent and invariant comments are to software what structural engineering notes are to a building's blueprints: the blueprint shows where the beams are, but the notes explain why that beam gauge was chosen and what maximum load it must sustain. Without those notes, a future renovation might remove a load-bearing wall. Similarly, without intent and invariant comments, a future refactor might silently violate a critical guarantee.

Connection to Formal Contracts and Static Analysis

Writing intent and invariant comments is not merely a stylistic preference—it is the informal precursor to formal specification. The C++ ecosystem is steadily moving toward making these annotations machine-checkable. Understanding the relationship between today's comments and tomorrow's contracts will position you to write comments that naturally evolve into verified specifications.

Today's comments vs. tomorrow's contracts
AspectNatural-Language Comment (Today)C++ Contracts (C++26 Proposal)
Precondition// PRE: n > 0pre(n > 0)
Postcondition// POST: result >= 0post(r: r >= 0)
EnforcementManual via assert() + code reviewCompiler-checked at build or runtime; configurable violation handling
Staleness riskHigh: comment may drift from implementationLow: if the contract expression no longer compiles or evaluates false, the build or test suite catches it
ExpressivenessUnlimited natural language: can reference domain knowledge, paper citations, non-code artifactsLimited to boolean expressions in C++; complex invariants still need natural-language supplements

Tools like Clang-Tidy, PVS-Studio, and SAL annotations (Microsoft's Source Annotation Language) already leverage structured comments and attributes to perform static analysis. By writing your intent and invariant comments in a disciplined, structured way today, you make it straightforward to migrate them to formal contracts when your compiler supports them. The habit of thinking in preconditions, postconditions, and invariants is more important than the syntax you use to express them.

🔮 Looking Ahead
Even with C++ contracts, natural-language comments will remain essential for expressing why a contract exists, referencing external specifications, or documenting constraints that cannot be encoded as a boolean C++ expression (e.g., "this function must complete within 10 μs for real-time audio").

Practice Problems

PROBLEM 1 — CONCEPTUAL
Explain in your own words the difference between a comment that explains intent and a comment that explains mechanics. Why is the former generally more valuable in a long-lived codebase?
PROBLEM 2 — BASIC
Given the following C++ code, replace the redundant comment with an intent comment and add a precondition comment: // This function searches the vector bool binary_search(const std::vector<int>& v, int target) { int lo = 0, hi = v.size() - 1; while (lo <= hi) { int mid = lo + (hi - lo) / 2; if (v[mid] == target) return true; if (v[mid] < target) lo = mid + 1; else hi = mid - 1; } return false; }
PROBLEM 3 — INTERMEDIATE
Consider a class BoundedQueue<T> that stores up to N elements in a circular buffer, supports enqueue() and dequeue(), and uses head_ and tail_ indices. Write a complete class invariant comment and a loop invariant for a drain() method that dequeues all elements into an output vector.
PROBLEM 4 — APPLIED
You are reviewing a pull request for a real-time sensor fusion module. The author added this code with no comments: Eigen::Matrix3d R = Eigen::AngleAxisd(yaw, Eigen::Vector3d::UnitZ()) * Eigen::AngleAxisd(pitch, Eigen::Vector3d::UnitY()) * Eigen::AngleAxisd(roll, Eigen::Vector3d::UnitX()); sensor_data.transform(R); Write a code review comment requesting specific intent and invariant comments, explaining what information is missing and why a future maintainer needs it.
PROBLEM 5 — CRITICAL THINKING
A senior engineer on your team argues: "Good code doesn't need comments. If you feel the need to write a comment, it means your code isn't clear enough—refactor instead." Construct a nuanced counter-argument that acknowledges the valid core of this claim while identifying the specific categories of knowledge that cannot be expressed through code alone, no matter how well-structured. Support your argument with at least two concrete C++ examples.

Summary

Effective C++ commenting centers on two categories of information that code alone cannot convey: intent (the reasoning behind design decisions, algorithm choices, and constant values) and invariants (the guarantees that must hold for correctness, expressed as class invariants, loop invariants, preconditions, and postconditions). These high-value comments sit at the top of the comment quality spectrum, far above redundant or stale annotations.

The practical discipline involves four steps: first, make code expressive through good naming and structure to minimize the need for comments; second, use structured prefixes like // WHY:, // INV:, // PRE:, // POST:, and // WARN: for scannability; third, back comments with runtime assertions where possible; and fourth, keep comments adjacent to their code and update them during every refactor. This habit directly prepares you for C++ contracts (targeting C++26), which will formalize preconditions and postconditions as compiler-checked language features.

Varsity Tutors • C++ • Writing Comments — Write comments that explain intent and invariants