Engineering Practitioner Brief / 18 May 2026
Dead Code Cost
The annual tax of carrying code that nobody runs.
Dead code is the politest form of technical debt. It does not break anything, it does not cause incidents, it does not show up on dashboards. It just makes every other piece of work slightly slower, every CI bill slightly higher, and every security scan slightly noisier. The cumulative cost per engineer per year sits between $8,000 and $60,000 depending on codebase size, language, and how much of the dead code is in hot read paths. This page breaks down where that range comes from and how to act on it.
15%
Median unused-function rate
Romano and Scanniello, 2018, Java sample
$8K-$60K
Annual cost per engineer
Range, varies by language and codebase size
2 to 6 hrs
Removal cost per 1,000 dead LOC
Median, with tooling and a short observation window
The Five Cost Vectors of Dead Code
Dead code is not free because it is shipped to production. Every line carries a small recurring tax. Each vector is individually modest, but they compound because the same line gets read, indexed, built, scanned, and reasoned about thousands of times across a codebase's life.
Vector 1: Read-time cognitive tax
Engineers spend the largest single share of their time reading code, not writing it. Microsoft Research has published instrumentation studies putting the ratio at 6:1 to 10:1 in favour of reading. When an engineer changes a function that calls helper-A, they also read helper-A, B, and C nearby. If half of those helpers are dead, the engineer still reads them because the IDE does not visually distinguish reachable from unreachable code by default. Conservatively this adds 3 to 8 minutes per dead helper per code-change interaction. Across a year of 200 working days and 5 to 10 such interactions per day, the per-engineer cost ranges from $1,500 to $4,500 at typical fully-loaded rates.
Vector 2: IDE and code-search latency tax
Every language server, every IDE indexer, every grep across a repo, every code-search tool (Sourcegraph, GitHub code search, internal greps) has to walk the dead code. For repos in the 100K to 1M LOC range the marginal cost of an extra 15 percent dead code is measurable: 1 to 3 seconds added to project-wide find-usages, 5 to 15 seconds added to a fresh language-server warmup, 200 to 800 ms added to symbol-search queries. Over a year these stack up to $400 to $1,800 per engineer.
Vector 3: CI build and test minutes tax
Dead code compiles. Dead modules sit in the dependency graph. Dead tests run (because the test files themselves are not dead, even though the production code they cover is). For a TypeScript monorepo, dead code typically adds 8 to 25 percent to a cold-cache full build. Tree-shaking in the bundler removes dead code from the production artifact but does not remove it from CI compilation, type-checking, or test runs. At typical GitHub Actions or CircleCI minute pricing (see dependency debt cost for related infra components), the per-team annual cost is in the $5,000 to $25,000 range for a team of 10 engineers, or $500 to $2,500 per engineer.
Vector 4: Security and compliance scan tax
Snyk, Dependabot, Semgrep, GitHub Advanced Security, and SAST tools scan every line. Dead code accounts for a proportional share of the false-positive triage workload. A 15 percent dead-code rate produces roughly 15 percent more security alerts to look at, even though almost none of the alerts on dead code are exploitable. The triage cost per false-positive averages 10 to 30 minutes when documenting the false-positive justification for compliance auditors. For a team running a serious AppSec program, this is the second-largest cost vector after read-time tax.
Vector 5: On-call and debugging tax
When an alert fires at 3am and the on-call engineer searches the codebase for the function name in the stack trace, dead code shows up alongside the real implementation. The wrong path gets read first more often than the right path because dead code tends to be the older, more deeply-nested implementation. The DORA State of DevOps reports consistently link low code quality to longer mean-time-to-recover; a portion of that MTTR penalty is the time spent reading dead code during incident triage. Per engineer per year this lands at $500 to $2,000 depending on on-call frequency.
A Worked Removal Example
To make the cost-of-removal concrete, consider a 250,000-LOC TypeScript monorepo with 12 engineers. A dead-code audit using Knip and runtime instrumentation identifies the following:
| Category | Dead LOC Found | Removal Effort | Tooling Used |
|---|---|---|---|
| Unused exports | 8,400 | 14 hrs | Knip + ts-prune |
| Stale feature flags | 3,100 | 28 hrs | LaunchDarkly export + manual |
| Deprecated REST endpoints | 2,200 | 22 hrs | Access-log analysis |
| Vendored libs no longer imported | 11,500 | 6 hrs | npm ls + delete |
| Sunset product modules | 14,000 | 40 hrs | Manual with stakeholder review |
| Total | 39,200 LOC | 110 hrs |
The removal effort cost at $85 per engineer-hour is $9,350. The annual carrying-cost savings against the midpoint of the per-engineer dead-code tax (call it $25,000) applied to 12 engineers is $300,000. The payback period is less than two weeks. The biggest reason teams do not run this audit annually is not the cost or the risk, it is that the audit does not ship a feature and therefore does not appear on most roadmaps.
How to Get Dead Code Off the Roadmap-Less List
Three patterns work in practice. The first is the standing 5 percent rule: every sprint, every engineer spends 4 hours on dead-code removal. No approval needed, no roadmap slot. The cumulative effect across a team of 12 is roughly 200 engineer-hours per quarter, which clears a 250K LOC monorepo of its dead-code tax over 18 months on a continuous basis.
The second is the audit-and-PR pattern: once a year, an engineer or small team runs the full audit, opens PRs grouped by category, and gives owners two weeks to object before the delete commits land. This is more efficient than the standing rule but harder to start because the up-front audit cost is visible.
The third is bundling: every legacy-code refactor (see legacy code refactoring cost) includes a mandatory dead-code sweep of the surrounding module. This bundles the cost into work that is already approved and exploits the fact that the engineer has the context loaded. It is the slowest pattern globally but the lowest-friction politically.
Language-Specific Detection Notes
Detection quality varies sharply by language. Static type systems and explicit imports make detection more reliable; dynamic dispatch, reflection, and string-name lookup make it less so.
- TypeScript / JavaScript: Knip, ts-prune, and Webpack / Rollup bundle analysis. Reliable for static imports, blind to runtime require, eval, and dynamic-property access. See JavaScript Node legacy cost.
- Python: Vulture, the unused-imports rule in Ruff, and coverage-based detection. Blind to attribute lookup, getattr, and pytest fixtures. See Python Django legacy cost.
- Java: IntelliJ inspections, the Spoon analysis library, and the deadcode4j Maven plugin. Blind to reflection, Spring autowire by type, and JNDI lookup. See Java Spring legacy cost.
- C# / .NET: Roslyn analysers with IDE0051, Resharper's unused-code inspections. Blind to dynamic, MEF composition, and reflection-based dependency injection. See .NET Framework legacy cost.
- Go: The official golang.org/x/tools/cmd/deadcode tool from 2023. The most reliable detector in any mainstream language because Go does not have runtime reflection cheap enough to mislead it.
- Rust: The compiler itself with #[warn(dead_code)] catches most cases. cargo-udeps for unused dependencies.
- Ruby: Hardest mainstream language for dead-code detection because of method_missing, send, and dynamic class reopening. Coverage-based runtime detection is the only reliable approach. See Ruby Rails legacy cost.
Related Reading
- Legacy code refactoring cost
- Dependency debt cost
- Test debt cost per sprint
- Documentation debt cost
- How technical debt destroys productivity
Frequently Asked Questions
What counts as dead code?
Any code that is shipped to production but never reached by any execution path. Common categories: unused functions, unreached branches behind permanently-false feature flags, deprecated endpoints with no callers, vendored libraries no longer imported, and entire modules left over from products that were sunset but never deleted.
How does dead code cost money?
Five vectors: cognitive load when engineers read it during nearby changes, slower IDE response and code search, longer CI build and test times, more surface area for security scanners to flag, and on-call confusion when production behaviour does not match the code an engineer is reading.
How much dead code is normal?
Industry studies suggest 5 to 30 percent of a typical codebase is dead, with the percentage rising sharply after 5 years of activity and after major refactors that leave callers behind. A 2018 study by Romano and Scanniello found a median of 15 percent unused functions across a sample of Java repositories.
Is it safe to delete dead code?
Safer than people think, less safe than tools claim. Static analysers miss code reached via reflection, dynamic dispatch, or string-name lookup. The standard approach is to instrument the suspect path with logging for two weeks, confirm zero hits, then delete in a single isolated commit that can be reverted.
Which tools find dead code reliably?
Knip and ts-prune for TypeScript, Vulture for Python, the Roslyn analyser pack for C#, unused-deps for Rust, deadcode for Go (golang.org/x/tools/cmd/deadcode), and Scalyr or Honeycomb plus structured logging for runtime confirmation. Coverage tools used as dead-code detectors are noisy but quick.
Should I delete a feature flag that has been false for two years?
Yes. Stale feature flags are one of the highest-yield dead-code categories because they typically guard 50 to 500 lines each. LaunchDarkly's own data, published in their 2023 Feature Flag Lifecycle report, shows flag-deletion rates well below creation rates across nearly all customer cohorts.