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.

Programming braces with a command terminal and binary digits.
Inside this lesson 10 sections 5 questions 6 figures

SECTION 1

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.

SECTION 2

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.

SECTION 3

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.

Comment Quality SpectrumHARMFULESSENTIALStale / Wrong// returns sum(actually returns average now)⚠ Actively misleadingRedundanti++; // incr i// set x to 5x = 5;Adds noise onlyInformative// Format: YYYY-MM-DDThh:mm:ssZClarifies formatIntent// We sort by age// first so the UI// shows seniors// at the top.★ Explains WHYInvariant// INV: size_ <=// capacity_ at// all public exit// points.★★ Formal guaranteeDecision Flowchart: Should I Write This Comment?Does the code already say this?NoYesDoes it explain WHY or a guarantee?Delete the comment ✗YesNoKeep the comment ✓Refactor code instead
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.

SECTION 4

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:

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; });

The left version forces the reader to reverse-engineer the scheduling strategy; the right version makes it explicit.

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∧guardbodyI{ 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.

SECTION 5

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.

High-Value Comment Taxonomy for C++High-Value CommentsIntent CommentsInvariant CommentsWarning CommentsAlgorithm ChoiceWhy this algorithmover alternativesfunctions, lambdasDomain RationaleBusiness rule orspec referenceconditionals, constsClass InvariantMust hold betweenpublic method callsclass declarationsLoop InvariantHolds at start ofevery iterationfor, while loopsPerf TrapsWarn against naivechanges that regresshot paths, cachesUB / SafetyFlag undefinedbehavior riskscasts, raw ptrsPre/Postcondition (applies to both Intent & Invariant)// PRE: vec is sorted in ascending order.// POST: returns iterator to found element, or vec.end().Bridges intent (why the caller must sort) and invariant (guarantee on return).Quick Reference Prefixes// WHY:// INV:// PRE:// POST:// WARN:Design choiceClass guaranteeCaller dutyCallee promiseDanger flag
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.

SECTION 6

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. 1

    Step 1 — State the class invariant at the top

    Begin 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. 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. 3

    Step 3 — Explain the intent behind memory ordering

    Inside 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. 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. 5

    Step 5 — Add a warning comment for the capacity constraint

    Near 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

SECTION 7

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.

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.

Strengths and pitfalls of intent/invariant commenting

✦

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.

SECTION 8

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.

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

Today's comments vs. tomorrow's contracts

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").

SECTION 9

Practice Problems

Question 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?
Show answer
A mechanics comment paraphrases what the code does (e.g., "increment counter"), which a reader can already determine by reading the code. An intent comment explains why the code does what it does (e.g., "we increment counter here to maintain a 1-based index expected by the legacy billing API"). Intent comments are more valuable because the rationale behind a design choice is not encoded in the syntax; it exists only in the programmer's mind. When a future developer encounters the code, they can read the mechanics themselves, but without an intent comment they may have to spend hours reverse-engineering the design decision—or worse, they may change the code in a way that violates the original reasoning.

Question 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; }
Show answer
A suitable intent comment and precondition would be: // WHY: We use binary search rather than std::find because the caller guarantees sorted input // and we need O(log n) lookup in the hot path of the trading engine's order matcher. // PRE: v must be sorted in non-decreasing order. Passing an unsorted vector yields undefined results. bool binary_search(const std::vector<int>& v, int target) { ... } The original comment "This function searches the vector" is redundant because the function name and signature already convey that information. The replacement explains why binary search was chosen (performance in a hot path) and documents the precondition (sorted input) that the code cannot enforce through its type signature alone.

Question 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.
Show answer
Class invariant: // INV: 0 <= size() <= N, where size() == (tail_ - head_) mod (N+1). buf_[head_..tail_) (mod N+1) contains valid, constructed T objects. head_ == tail_ implies the queue is empty. Loop invariant for drain(): // INV (loop): At the start of each iteration, out contains exactly (original_size - current_size) elements dequeued in FIFO order, and the class invariant still holds for the remaining elements. The class invariant anchors every method's reasoning: any public method may assume it on entry and must re-establish it on exit. The loop invariant for drain() is specifically useful for proving that, upon termination (when current_size reaches 0), out contains all original elements in the correct order and the queue is empty—satisfying drain()'s postcondition.

Question 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.
Show answer
A good review comment would be: "Please add an intent comment explaining (1) why we compose the rotation in ZYX (intrinsic) order—is this matching the IMU's convention, the ROS REP-103 standard, or some other specification? (2) What coordinate frame this rotation transforms from and to (e.g., body frame to NED world frame). Also add an invariant comment stating that R must be a valid rotation matrix (orthogonal, determinant = +1) and that yaw/pitch/roll are expected in radians, not degrees. Without these comments, a future developer might swap the rotation order to XYZ thinking it is equivalent, or pass angles in degrees, producing a rotation that is numerically valid but physically meaningless. This kind of silent semantic error is extremely difficult to debug in a sensor fusion pipeline." The key insight is that the code is syntactically correct and compiles without issue, but the domain conventions that make it semantically correct are invisible.

Question 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.
Show answer
The senior engineer is correct that comments compensating for poor naming, convoluted logic, or excessive function length are a code smell—the right response is refactoring, not annotating. However, the claim overreaches because several categories of knowledge are fundamentally non-expressible in C++ syntax. First, design rationale: if you chose a B-tree over a hash map because the workload is range-query-heavy and you benchmarked a 3× improvement, no amount of renaming encodes the benchmark result or the alternative considered. Code shows what you chose; only a comment shows what you rejected and why. Second, external specification references: a line like constexpr double MAX_JERK = 2.5; cannot express that 2.5 m/s³ comes from ISO 18738-1 Section 4.3 for elevator comfort. Third, threading contracts cannot be enforced by the C++ type system (absent contracts): // PRE: must be called from the main thread is information that exists outside the code's expressible domain. Fourth, invariants that span multiple data members—e.g., "size_ counts only non-tombstoned entries in the hash table"—describe relationships the type system cannot capture. The nuanced position, therefore, is: refactor first to minimize the need for comments, then write intent and invariant comments for the residual knowledge that clean code still cannot communicate.

SUMMARY

Summary

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