A teammate leaves a 40-line block of commented-out code in a pull request, arguing "we might need it back". What concrete harms does that cause, and is there any situation where keeping it is defensible?
answer
- dead text no compiler/test/refactor touches
- goes stale immediately, false 'it worked' comfort
- nobody dares delete → accumulates forever
- VCS owns deleted lines with context
- use flag / skipped test / commit link instead
basics
~20 sIt is dead text nothing checks: no compiler, no tests, no refactoring tool touches it, so it silently goes stale. Readers won't delete it because they assume it matters. Version control already stores it — delete it and recover from history if needed.
solid answer
~50 sHarms: (1) it decays instantly — renames, signature changes and automated refactorings skip commented text, so within weeks it no longer compiles or reflects reality; (2) it is untested and uncovered, giving false comfort that it "still works"; (3) it creates reader paralysis — nobody dares delete code someone deliberately preserved, so it accumulates; (4) it pollutes search, diffs and review, and can hide the real logic; (5) it is redundant, because version control retains every deleted line with full context, author and message. The correct move is to delete it and, if it might matter, reference the commit or issue in a one-line comment or the PR description. Defensible exceptions are narrow: your own local scratch work before commit; a deliberately disabled fixture or benchmark that a comment explains and a ticket tracks; or generated/vendored files you do not own. A better substitute is almost always a feature flag, a skipped test, or a documented branch/tag.
code
pseudocode · 10 lines// BAD - dead, unchecked, will not compile after the next rename
// function oldPricing(order) {
// return order.subtotal * TAX_RATE; // TAX_RATE was deleted last month
// }
function pricing(order) { return taxEngine.total(order); }
// BETTER - deleted, with a pointer to where the old logic actually lives
// Legacy flat-rate pricing removed in commit 9f2c1ab; see ADR-021 for why
// the tax engine replaced it.
function pricing(order) { return taxEngine.total(order); }go deeper
Say it is dead code that version control already stores, that it goes stale, and that it should be deleted before merge.
Enumerate the concrete harms — staleness under refactoring, no test coverage, reader paralysis, search/diff pollution — and name better mechanisms: feature flag, skipped test, commit reference.
Distinguish shared-branch policy from local debugging, cover the narrow exceptions (documented+tracked disable, sample config, generated files), and mention linter rules plus review norms for enforcement.
Generalise to the rule that unverified text must not duplicate what a tool owns, and tie it to codebase hygiene at scale: dead-code budgets, flag lifecycle policy, and preventing accumulation across hundreds of repositories.
## What "commented-out code" actually is It is source code that has been turned into a comment — `//` prefixes, `/* ... */` wrappers, `#` marks — so it no longer executes but still occupies the file. People do it as an informal undo: *disable now, maybe restore later.* ## Why it is treated as a defect, not a neutral habit **1. Nothing maintains it.** Live code is protected by a chain of automatic checks: the compiler or interpreter, static analysis, linters, tests, and IDE refactorings. Commented text is invisible to every one of them. Rename a method, change a constructor signature, delete a field — the live call sites are updated, the commented one is not. The block is stale almost immediately, and restoring it later usually produces something that does not even compile, let alone behave correctly. **2. It is untested and uncovered.** Nobody runs it, so coverage tools do not report it, and there is no evidence it ever worked in the current version of the system. Its presence implies "proven alternative", which is false. **3. It causes reader paralysis.** A future maintainer sees deliberately preserved code and reasons: *someone kept this on purpose; deleting it might lose something.* So they leave it. The next person does too. Commented blocks therefore accumulate monotonically — they are almost never removed by anyone other than the original author, who has forgotten about them. **4. It degrades every tool that reads text.** Searching for a symbol returns hits inside dead blocks. Diffs and code review get longer. In some languages a commented block can even change behaviour indirectly — for example when a nested `/* */` terminates early, or when a linter directive inside it is still honoured. **5. It duplicates version control, badly.** Git and equivalents store every deleted line permanently, with the commit message, author, date, and the surrounding change as context. `git log -S<string>`, `git blame`, and history browsing retrieve it precisely. The commented block has none of that context — you cannot tell *when* it was disabled, *why*, or what state the rest of the file was in. The tool version is strictly better along every dimension. ## What to do instead | Intent | Better mechanism | |---|---| | "We may switch back" | Delete it; note the commit SHA or PR link in the message or a one-line comment. | | "Toggle between two behaviours" | A feature flag or configuration switch — both paths stay live and testable. | | "This test is broken right now" | A skipped/ignored test with a reason and a tracked issue, so it stays visible and compiles. | | "Keep an alternative implementation" | A real implementation behind an interface (a strategy) with tests, or a documented branch/tag. | | "Debug scaffolding" | Keep it local, or convert it to trace-level logging that ships. | ## Legitimate exceptions - **Local, uncommitted work.** Commenting things out while debugging is perfectly normal; the rule is about what reaches the shared branch. - **Documentation-by-example.** A commented sample invocation in a configuration file or a public header (`# uncomment to enable verbose mode`) is documentation aimed at a user, not dead application logic — and it is fine, because it is *intended* to be inert and is usually part of the file's contract. - **Deliberately disabled, explained and tracked.** A quarantined benchmark or a workaround snippet with `// Disabled pending vendor fix #8812 — see ADR-021` at least carries the missing context. Even then, a skipped test or a flag is usually cleaner. - **Generated or vendored files** you do not own and must not hand-edit. ## How teams enforce it Most linters ship a rule for this (commented-out-code detectors in ESLint plugins, detekt, SonarQube, RuboCop, etc.). Because heuristics can misfire on prose that resembles code, teams typically run it at warning level plus a review norm: *if a reviewer sees a commented block with no explanation, it gets deleted before merge.* ## The underlying principle This is the same rule that condemns journal comments and author attributions: **do not duplicate in unverified text what a tool already owns and keeps correct.** Version control owns history. Feature flags own toggles. Test runners own disabled tests. Comments own rationale — and only rationale.
- Your teammate says "but it's easier to uncomment than to dig through git history". How do you answer?Convenience once is traded against permanent cost for every reader, and the convenience is illusory: after a few refactorings the block no longer compiles, so restoring it is a rewrite anyway. Recovering from history is a single command and gives you the commit message and surrounding context you would otherwise lose.
- What is the right way to keep two competing implementations available in production code?Make both live and tested behind a common interface, and select between them with a feature flag or configuration. Both paths then compile, are covered by tests, and can be rolled out or rolled back at runtime — none of which a commented block gives you.
It is like keeping an old, disconnected wire coiled inside an electrical panel because you might reuse it. Nobody inspects it, nobody knows if it is still rated for the load, and every future electrician leaves it there out of caution — so the panel only ever gets more crowded.
saying these in an interview costs you the question
- "Keep it, we might need it back" — history already keeps it, with context the comment lacks.
- "It documents the old behaviour" — it documents a version that no longer compiles; a commit link or ADR does the job correctly.
- "It's harmless, it doesn't run" — it costs reading, review, search, and blocks deletion by later readers.
- Believing commented-out tests still prove anything about current behaviour.
- Using comment-out as a permanent feature toggle instead of a flag or configuration.