← BackJan 4, 2026

When Comments Should Explain ‘What’ and When They Should Explain ‘Why’

Choosing between explaining the *what* or the *why* of code in comments is a nuanced decision. While descriptive code should carry the meaning, some scenarios still warrant a brief explanation to avoid context switches and misunderstandings. This article explores the trade‑offs, illustrates with concrete examples, and offers practical guidelines for engineers.

### The Long‑Standing Rule of Comments Many developers follow the adage that comments should explain *why* something is done, not *what* it is doing. The logic is simple: the code itself, using clear names and structure, should reveal the *what*. If you find yourself writing a comment that merely repeats the code, the code is likely unclear. However, in practice this principle can clash with real‑world constraints. A comment that points out why a line is repeated, or why a particular implementation choice has been made, provides value that a refactor alone might not capture. The key is to balance clarity with maintainability. ### A Case Study: Variable Naming vs. Commenting Consider the following snippet: ```java // weight, radius, price w = 10, r = 9, p = 1 ``` Here a terse comment attempts to explain each variable. Without the comment, the reader would have to infer that `w` is *weight*, `r` is *radius*, and `p` is *price*. While the variables are short, the comment forces the reader to **scroll back and forth** every time they encounter `w` later in the code. This context switch can be especially disruptive when the variables are used in a complex algorithm. Contrast this with a self‑documenting alternative: ```java weight = 10; radius = 9; price = 3; ``` By using explicit names, the code is immediately readable, and no comment is necessary. The lesson? Prefer expressive identifiers over *what* comments whenever possible. ### Why Some People Prefer Commit Messages A frequent argument against *why* comments is that they should live in commit messages or tests, keeping the source code clean. Imagine this line in production code: ```java // Clear twice to deal with bug ABC in library XYZ, see [link] XYZ.clear(); XYZ.clear(); ``` If the comment is removed and the explanation documented in the commit, any developer reading the code in the future has to hunt down the relevant commit. That requires a context switch and an extra mental cost. Moreover, if the line is refactored or reordered, `git blame` may no longer point to the original commit, breaking the link between code and rationale. For a team that values **immediate readability**, embedding a concise rationale in the code itself can save countless hours of digging. ### Refactoring vs. Commenting: The Bob Martin Example Bob Martin’s *Extract‑Till‑You‑Drop* technique encourages breaking complex logic into tiny methods. The refactored version of a symbol replacement routine looks like this: ```java String replace() { replaceAllSymbols(); return stringToReplace; } private void replaceAllSymbols() { for (String symbolName = nextSymbol(); symbolName != null; symbolName = nextSymbol()) replaceAllInstances(symbolName); } private String nextSymbol() { /* 
 */ } private void replaceAllInstances(String symbolName) { /* 
 */ } private boolean shouldReplaceSymbol(String symbolName) { /* 
 */ } private void replaceSymbol(String symbolName) { /* 
 */ } private String symbolExpression(String symbolName) { /* 
 */ } ``` While the refactor pulls the bulk of the logic into separate methods, a developer now must **jump across method boundaries** to grasp the flow. Conversely, keeping the logic in a single method and adding explanatory comments can preserve the *in‑place* understanding. For example, the original single‑method implementation with informative comments is easier to read for someone tracking a bug: ```java String replace() { Pattern symbolPattern = Pattern.compile("\\$([a-zA-Z]\\w*)"); // e.g. $F1a3 Matcher symbolMatcher = symbolPattern.matcher(stringToReplace); // Replace all symbols while (symbolMatcher.find()) { String symbolName = symbolMatcher.group(1); // translate replaces all instances; run once per symbol if (getSymbol(symbolName) != null && !alreadyReplaced.contains(symbolName)) { alreadyReplaced.add(symbolName); stringToReplace = stringToReplace.replace("$" + symbolName, translate(symbolName)); } } return stringToReplace; } ``` In this scenario, the *why* comment clarifies the intent of `translate` and the reasoning for the guard clause. The benefit outweighs the tiny increase in lines. ### Practical Guidelines 1. **Prioritize descriptive names**. If a variable or method can be named to reveal its purpose, do so. Avoid *what* comments. 2. **Use *why* comments sparingly but where they reduce cognitive load**. When an idiom, workaround, or historical quirk is necessary, document it. 3. **Keep related comments near the code**. Whether the comment explains *what* or *why*, proximity matters. Don’t hide explanations in commit logs if the code may be read independently. 4. **Avoid redundant comments**. A comment that simply repeats the code is a maintenance burden. 5. **Leverage tests and documentation for additional rationale**. Unit tests can validate the *why*—for example, that a certain edge case is handled. 6. **Maintain consistency across the codebase**. Agree on guidelines with your team and enforce them in code reviews. ### Conclusion The dichotomy of **what** vs **why** comments is not absolute. Good code often conveys the *what*; additional *why* comments are justified when they reduce cognitive friction and help future maintainers. Striking the right balance—using expressive code as the first line of defense and augmenting it with succinct comments for complex or non‑intuitive decisions—leads to more robust, self‑documenting systems.