Dead Code: How to Find It, Remove It and Prevent It
A practical guide to dead code: how to find unreachable and unused code, safely remove it and prevent it from accumulating in your codebase.
In this article:
- What is dead code and why it accumulates
- The real costs of dead code in production systems
- How to find dead code: tools and techniques
- How to refactor code: safely removing dead code
- How to prevent dead code from returning
- Conclusion
What is dead code and why it accumulates
Dead code is any code that exists in the codebase but is never executed in a live environment. It includes unreachable branches (code after a return statement, branches of conditions that can never be true), unused variables and parameters, functions that are never called, classes that are never instantiated, feature flag conditions that are always false, commented-out blocks left for future reference and deprecated APIs that are still present but no longer used.
Dead code accumulates for predictable reasons. Features get removed but the implementation does not. A refactoring replaces an old function but does not delete it “just in case.” A flag to roll back a feature release becomes permanently false but stays in the code. A class is superseded by a new implementation but deletion feels risky because no one is certain nothing calls it.
Over time, each of these individual decisions creates a codebase where significant portions of the code serve no purpose except to confuse developers, inflate binary sizes and create phantom dependency chains.
Understanding codebase health requires confronting dead code directly. In every codebase we assess at Eden Technologies, dead code consistently represents between 10% and 25% of the total file count. In one case, a codebase of 400,000 lines contained over 80,000 lines of code that were never reached by any code path.
The real costs of dead code in production systems
Dead code has costs that engineering teams underestimate because the code “is not doing anything.” That reasoning is incorrect.
Cognitive overhead. Every developer who reads the codebase has to parse dead code along with live code. When a new engineer joins a team and sees a function that looks important but is never called, they spend time trying to understand its purpose. When they find a feature flag that has been false for two years, they have to decide whether to trust it or investigate it. This overhead is not zero. It adds to onboarding time and to the friction of every code review and debugging session.
Build and dependency inflation. Dead code that imports libraries or frameworks causes those dependencies to be included in the build. In compiled languages, this inflates binary sizes. In interpreted languages with dependency trees, it increases startup time and attack surface. One JavaScript application we reviewed had twelve npm packages that were imported only by dead code that was never reachable in any deployment environment.
Security risk. Dead code that contains security-sensitive operations is particularly dangerous. An old authentication endpoint that is “never called” may still be reachable via an undocumented URL. A deprecated data export function that bypasses access controls may still execute if someone discovers it. The security posture of a system is not defined by its intended code paths but by all code paths, including the unintended ones.
False coverage metrics. Coverage tools count dead code differently depending on their configuration. Code that is technically present but unreachable may inflate coverage numbers, giving a false sense of security about test adequacy.
How to find dead code: tools and techniques
Finding dead code requires both automated tooling and manual review, because automated tools have limitations in dynamic languages and at runtime boundaries.
Static analysis. Tools like SonarQube, JaCoCo (Java), Istanbul (JavaScript) and Coverage.py (Python) can identify code that is never referenced from any call site in the codebase. This catches unused functions, classes and imports effectively. The limitation is that static analysis cannot detect code that is reachable via reflection, dynamic dispatch or external configuration.
Coverage analysis. Running the full test suite with coverage instrumentation shows which lines are executed during testing. Lines with zero coverage are candidates for dead code review, though the absence of test coverage is not the same as unreachability in production.
Runtime tracing. For systems where static analysis is insufficient (heavily dynamic codebases, systems with extensive use of reflection or eval), production traffic analysis can identify endpoints, functions or modules that receive zero traffic over an extended period. This is the most reliable method but requires observability infrastructure.
Manual review during sprint work. When a developer touches a module for a feature change, a standing practice of “remove dead code you encounter” prevents accumulation at the margin. This requires a culture where deletion is treated as safe by default, supported by version control history.
How to refactor code: safely removing dead code
The question most engineering teams have about dead code removal is: how do we know it is safe to delete? The concern is valid. Removing code that turns out to be called in an unexpected context creates a production incident.
The safe deletion process follows these steps.
First, confirm unreachability through multiple signals: static analysis shows no callers, coverage analysis shows zero execution in tests and (where possible) runtime metrics show zero invocation in production over the last 90 days.
Second, add a log statement or monitoring alert to the code before deleting it. Deploy this version to production and observe for 72 hours. If the log fires, something is calling the code. Investigate before deleting.
Third, when confident the code is unreachable, delete it. Do not comment it out. Do not move it to an archive folder. Delete it. The version control history preserves every line ever written. A comment that says ”// old implementation, remove later” is dead code that will outlive everyone who made that comment.
Fourth, run the full test suite after deletion to confirm no tests relied on the dead code.
For larger-scale dead code removal across many files, consider a dedicated sprint or time-boxed effort focused solely on deletion. This approach, combined with the verification steps above, allows a codebase to shed significant weight safely.
How to prevent dead code from returning
Prevention is more valuable than remediation. Dead code accumulates when there is no process signal that prevents its introduction.
Feature flag lifecycle management. Every feature flag should have a defined expiry: a date or release milestone after which the flag is removed regardless of whether the rollout is complete. This prevents permanent flags from becoming permanent dead code.
Definition of done. The sprint definition of done for feature removal should include “all associated code, tests and configuration deleted.” This makes deletion an explicit deliverable rather than a deferred task.
Automated detection in CI. Tools like ts-prune for TypeScript or vulture for Python can detect unused exports and dead functions as part of the pull request process. A CI check that fails on newly introduced dead code prevents accumulation at the point of entry.
Regular cleanup sprints. A quarterly half-sprint dedicated to codebase health, including dead code removal, reinforces the practice and prevents gradual drift.
Our legacy modernization assessments always include dead code mapping as a first step, because removing it before refactoring reduces the scope of the refactoring significantly.
Conclusion
Dead code is not harmless. It creates cognitive overhead, inflates dependencies, introduces security risk and produces false signals in coverage and health metrics. In codebases with significant accumulation, it can represent 20% or more of the total codebase size.
Removing it safely requires a verification process, not just a deletion command. Preventing it from returning requires process changes in sprint workflow and CI pipeline.
Eden Technologies treats dead code removal as a high-value, low-risk first step in any codebase remediation. In one engagement, removing dead code reduced the effective codebase by 18% before any structural refactoring began, which simplified every subsequent remediation task.
Does your codebase have these problems? Let’s talk about your system