Dead code is program fragments that are never executed and do not affect the result, but physically remain in the project source files. Unlike commented-out sections, dead code is compiled and ends up in the binary, increasing its size and complicating navigation. According to TIOBE Index (2025), the average commercial project contains 10 to 25 percent of code that is never called. Zombie code is a subtype of dead code that worked in the past but lost its relevance after refactoring and now just takes up space. Regularly cleaning such fragments reduces the cognitive load on developers and decreases the risk of errors when making changes.
Key Takeaways
Dead code is source code that is included in the program but is never executed under any usage scenario. The compiler or interpreter processes it, but at runtime, control never reaches these sections.
Classic examples of dead code: variables that are assigned a value but never read; functions or methods that are never called; conditional branches that never become true (if(false)); loops whose body never executes.
According to the SonarQube State of Code Quality (2025) report, about 15 percent of all warnings in commercial Java projects are related to unused private methods and fields. In JavaScript projects, the share of unused code can reach 30 percent due to the dynamic nature of the language and the abundance of third-party libraries.
Regularly check your project for dead code — especially after major refactorings and feature removals. One forgotten import or unused function today can turn into zombie code tomorrow, misleading new team members.
Zombie code is a special case of dead code distinguished by its historical context. Zombie code once worked, but after changes in the system it became unreachable, yet it was not removed and was left “just in case.”
The difference between dead and zombie code lies in origin. Dead code might have been written erroneously (never worked), while zombie code is former live code that lost its relevance during refactoring. For example, a discount calculation function based on old business logic that was replaced by a new one, but the old method was not removed — in case it needs to be restored.
The main danger of zombie code is the illusion of working functionality. A new developer sees a function, reads its documentation, assumes it is called somewhere — and wastes time studying an artifact. When trying to call it directly, it may turn out that it depends on deleted entities or outdated APIs.
Track zombie code through git history: if a function has not been modified in two years and is not used — it is a zombie. Remove it without hesitation because git stores history, and the code can always be restored if needed.
The first and most common reason is iterative development with incomplete refactoring. The team adds new functionality replacing the old one but does not remove the replaced modules. Sprints accumulate such “tails,” and after a year the project becomes overgrown with a layer of dead code.
The second reason is A/B testing and feature toggles. Conditions for enabling a new feature may become fixed over time (e.g., always true), but the else branch with alternative logic remains in the code. Developers are afraid to remove it in case they accidentally break the system if the toggle is switched back.
The third reason is code generation and copy-paste. Code generators (IDEs, template engines) create stubs with methods that the developer does not fill in or use. Code copied from another project often contains entire blocks irrelevant to the new context.
The fourth reason is fear of deletion. In large projects, developers are afraid to remove code because they are not sure it is really unused. This fear is exacerbated by a weak test system: without automated checks, deletion may lead to bugs that are only discovered in production.
Dead code directly affects four aspects of project quality: build performance, artifact size, team cognitive load, and refactoring reliability.
Increased compilation time: the compiler processes unused files, analyzes dependencies, and generates bytecode or machine code for fragments that will never run. In large projects, this adds minutes to each build. For interpreted languages (JavaScript, Python), module loading time and memory consumption increase.
Risk of bugs during modification: a developer changing code does not suspect that a function is only used in a dead branch. After refactoring, the dead code stops compiling or produces errors — the team spends time diagnosing a problem that does not affect the application.
Cognitive load is the most expensive factor. Every unused function requires attention when reading code. A developer spends mental energy understanding why this code exists and where it is called. A Developer Productivity Lab study (2025) showed that removing 20 percent of dead code reduces onboarding time by an average of 18 percent.
Remove dead code immediately upon detection. Every day of delay increases the likelihood that someone on the team will spend hours studying an artifact that should have been removed yesterday.
Dead code detection is performed using two main methods: static analysis (without running the program) and dynamic analysis (runtime coverage profiling). Each approach is effective for different types of dead code.
Static analyzers support all popular programming languages. For Java and Kotlin — SonarQube, IntelliJ IDEA Inspections, SpotBugs. For JavaScript and TypeScript — ESLint with no-unused-vars and no-unused-modules rules. For Swift — SwiftLint with the unused_declaration rule. For Python — pylint with the unused-import option and vulture for deep search.
// build.gradle.kts - ProGuard configuration for Android
android {
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
// proguard-rules.pro - keep only required classes
-keep class com.example.app.** { *; }
-assumenosideeffects class Timber {
static <methods>;
}
ProGuard not only removes unused classes and methods but also minifies names in release builds. A build with ProGuard enabled automatically shows which classes and methods are considered unused — the usage.txt report lists all removed code.
Code coverage tools (JaCoCo for Java, XCTest coverage for Swift, Istanbul for JavaScript) show which lines and branches are executed during tests. Methods with zero coverage are candidates for dead code. However, lack of coverage does not guarantee the code is not called in production — for complete confidence, use a combination of static and dynamic analysis.
Configure your CI pipeline so that the build fails when the threshold of unused declarations is exceeded. A SonarQube Quality Gate with the rule “Share of unused private code no more than 3%” prevents dead code accumulation at the development process level.
The process of removing dead code consists of four steps: find, verify, delete, verify again. Skipping any step increases the risk of regression.
The first step — find candidates via a static analyzer. Get a report of unused declarations: functions, classes, variables, imports. Filter out false positives — analyzers sometimes make mistakes with reflection, dynamic class loading, or hidden calls via serialization.
The second step — check via git blame and change history. Look at when and why the code was written. If the code was part of a feature disabled by a feature toggle — make sure the toggle is fixed and will not be turned back on. Comment out code you are unsure about removing and leave a TODO with a ticket for re-checking in a month.
The third step — delete in a separate branch and run the full test suite. If tests pass — the likelihood of regression is low. If tests fail — the code is still used, and you need to figure out in which scenario.
// before - dead code and zombie code in same file
int calculateV1(int price) { // not called anywhere
int tax = price * 0.18;
return price + tax;
}
int calculateV2(int price, double rate) {
return static_cast<int>(price * (1 + rate));
}
// after - dead code removed, zombie code cleaned
int calculatePrice(int price, double rate) {
return static_cast<int>(price * (1 + rate));
}
The fourth step — code review of changes. The reviewer must confirm that the code is indeed dead. If the reviewer is not sure — leave a comment in the code and postpone deletion until a full analysis. After merging the branch — delete the branch to avoid proliferating zombie code in the git repository.
Adopt a rule: no pull request should introduce new dead code. Add a linter to pre-commit hooks that blocks commits with unused variables or imports. Prevention is always cheaper than cleanup.
Frequently Asked Questions
Yes, if dead code contains syntax errors or references deleted types. Modern compilers still check dead branches, so an error in an if(false) block will cause a build failure. This is a safety measure: code should not be so dead that the compiler does not check it.
Zombie code is misleading: a new developer sees a function with documentation and assumes it is in use. They spend time studying non-working code and may accidentally tie new logic to an outdated entity, creating a hard-to-find bug.
Use ESLint with the no-unused-vars and no-unused-modules rules, as well as the knip utility — it analyzes exports and imports across the entire project, finding unused files, functions, and dependencies. For large monorepos, knip provides the most comprehensive picture.
It is better to remove dead code before the release, but not at the last moment. Removing dead code is technical work that should be planned separately in a sprint. Immediately before a release, removal may introduce instability if the code turned out not to be as dead as it seemed.
Yes, modern compilers and minifiers (ProGuard, R8, Terser, Closure Compiler) remove unreachable code at the Dead Code Elimination level. However, this does not eliminate the need to clean up source files: the compiler removes code from the binary but not from the repository — developers still stumble over it when reading.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also