How to Find Dead Code in Your Repository (and Why You Should)
Every codebase is a graveyard of good intentions. In the rush to ship features, we often focus on what to add, rarely on what to remove. Over time, your repository accumulates "ghost code"—functions that are no longer called, modules that have been superseded by newer versions, and entire files that sit unreachable in the file system. If you want to maintain a high-velocity engineering team, you must learn how to find dead code and systematically prune it.
Dead code isn't just a cosmetic issue; it's a tax on your development cycle. It increases build times, bloats bundle sizes, and—most importantly—creates cognitive overhead for every engineer who has to navigate the repository. When a developer spends thirty minutes trying to understand a complex utility function only to realize it isn't even used, that is pure waste.
In this guide, we will explore why dead code accumulates, how to detect unused code at scale, and how to use automated tools like repowise to maintain a lean, performant codebase.
What Is Dead Code and Why Does It Matter?
In software engineering, dead code refers to any part of the source code that is executed but its result is never used, or code that can never be executed in the first place (unreachable code). While modern compilers are excellent at "tree-shaking" or stripping dead code during the build process, the source code remains in your repository, haunting your IDE and your teammates.
Types of Dead Code
To effectively remove dead code, you first need to categorize it:
- Unreachable Files: Files that exist in the directory structure but are never imported by any entry point. These are common after large refactors where a folder was replaced but the old one wasn't deleted.
- Unused Exports: Functions, classes, or constants that are exported from a module but have no consumers in the rest of the application.
- Zombie Packages: Dependencies listed in your
package.jsonorrequirements.txtthat are no longer imported anywhere in the source. - Logic Branches: Code paths inside a function that can never be reached due to upstream conditions (e.g.,
if (false) { ... }or checks for environment variables that are no longer set).
The Cost of Dead Code (Maintenance, Confusion, Security)
The presence of dead code is a primary driver of technical debt. It impacts your team in three specific ways:
- Maintenance Overhead: Every line of code is a line that might need to be updated during a global refactor or a dependency upgrade. If you are updating a library's API, you shouldn't waste time fixing callsites that aren't even active.
- Cognitive Load: For a new engineer joining the team, the codebase is a map of the system. Dead code represents "fake" landmarks. It makes the system seem more complex than it actually is, leading to slower onboarding and more frequent bugs.
- Security Risk: Dead code is often unmaintained code. If a vulnerability is discovered in an unused utility function, it still shows up in security scanners. Even worse, if an attacker finds a way to trigger that "unreachable" code via a dynamic exploit, it provides an unmonitored surface area for attacks.
Manual Dead Code Detection: Why It Doesn't Scale
For small projects, you might try to find dead code by manually searching for string occurrences of a function name. However, this approach quickly fails in professional environments.
Modern applications use dynamic patterns that defeat simple grep commands. Consider a TypeScript project where a component is imported dynamically, or a Python project using getattr() to call functions based on strings. Furthermore, in large monorepos, a function might be "used" in a test file but never in production, leading to a false sense of necessity.
Manual unused code cleanup is a losing game. It is reactive, error-prone, and consumes the most expensive resource in your company: senior engineer time. To maintain a healthy repository, you need an automated dead code detection tool that understands the underlying dependency graph of your code.
Dead Code Detection via Graph Analysis
How to Detect Unused Code via Automated Analysis
Automated detection moves beyond string matching. It uses Abstract Syntax Trees (ASTs) to build a mathematical model of your codebase. By understanding how repowise's architecture parses different languages, we can see how it identifies various forms of waste.
Import/Dependency Graph Analysis
The most robust way to find dead code is to build a directed graph where every file is a node and every import is an edge. By starting at your known entry points (like main.py, index.ts, or app.go) and traversing the graph, any node that remains unvisited is, by definition, unreachable. You can see this logic in action by exploring the FastAPI dependency graph demo.
Unused Export Detection
This is more granular. A file might be reachable, but it might export five functions while only two are used. A sophisticated tool parses the export statements and cross-references them with import statements across the entire repository.
Zombie Package Detection
By comparing the list of installed dependencies against the actual import statements in the AST, tools can identify libraries that are being bundled and shipped but never actually invoked. This is one of the fastest ways to reduce your application's attack surface and build size.
Confidence Scoring
Not all "unused" code is actually dead. Some might be part of a public API for a library, or accessed via reflection. High-quality tools provide a confidence score. A file with zero imports and no dynamic references has a 99% confidence rating for removal, whereas a function that matches a string in a configuration file might have a lower score.
Using repowise as a Dead Code Detection Tool
Repowise is designed to be a "codebase intelligence" layer. While many tools focus solely on linting, repowise combines git history, dependency analysis, and LLM-powered insights to provide a holistic view of code health.
Running the Analysis
To start a code cleanup with repowise, you can run the analysis via the CLI or self-host the platform. Repowise scans your repository, parses the imports for 10+ supported languages (including Python, TypeScript, Go, and Rust), and identifies disconnected components.
# Example: Running a dead code scan via repowise CLI
repowise scan --dead-code --min-confidence 0.8
Understanding the Output
The output isn't just a list of files; it's a prioritized report. Repowise ranks dead code by its "weight"—how many lines of code and how many transitive dependencies would be removed if you deleted that specific module. This helps you prioritize high-impact removals. You can see how these reports are structured in our live examples.
Dead Code Analysis Dashboard
Confidence Scores Explained
Repowise uses a multi-factor algorithm to determine confidence:
- Static Analysis: Does an import exist? (High weight)
- Git Intelligence: Has this code been touched in the last 12 months? If it's old and unused, confidence increases.
- Search Context: Does the symbol name appear in comments, strings, or config files? (Decreases confidence)
Cleanup Impact Estimation
Before you delete anything, repowise estimates the impact. If you remove a "dead" file that actually has 20 internal functions, you are reducing the maintenance burden significantly. Repowise calculates the "Cleanup Impact Score" to help you justify the refactor to your stakeholders.
A Step-by-Step Cleanup Workflow
Don't try to remove dead code all at once. Large "cleanup" PRs are a nightmare to review and often introduce regressions. Follow this structured workflow:
1. Run Detection
Execute your detection tool and generate a full report. Filter for "Unreachable Files" first, as these are the safest to remove.
2. Review High-Confidence Results First
Focus on results with a confidence score >95%. These are typically files that have been orphaned by previous refactors. Check the hotspot analysis demo to see how code activity correlates with importance.
3. Verify with Tests
Even if a tool says code is unused, run your full test suite. If your tests are comprehensive, they should catch any accidental deletions. If the code is truly dead, no tests should fail (unless you are deleting the tests themselves).
4. Remove in Small PRs
Group your deletions by module. This makes it easier for reviewers to verify that the code isn't being used dynamically elsewhere.
Common Dead Code Patterns
When you start to find dead code, you will notice recurring patterns. Recognizing these helps you prevent them in the future.
- Feature Flags Never Removed: A feature was rolled out 100% six months ago, but the
if (featureEnabled)block and the "old" implementation are still in the codebase. - Deprecated APIs Still in Tree: You created
v2of an internal API, migrated all your calls, but leftv1"just in case." - Test Utilities That Lost Their Tests: A helper function created for a specific unit test that was later deleted. The helper remains, imported by nothing.
Using get_dead_code() MCP Tool
One of the most powerful features of repowise is its integration with the Model Context Protocol (MCP). If you use AI agents like Claude Code, Cursor, or Cline, you can expose your codebase intelligence directly to them.
The get_dead_code() tool allows an AI agent to query repowise for unused exports or unreachable files within a specific scope. Instead of you manually hunting for code to delete, you can prompt your AI agent: "Find all unused exports in the /services directory and create a PR to remove them."
The agent will call the tool, receive a structured JSON response of dead code candidates, verify them by checking the dependency path, and then perform the cleanup.
MCP get_dead_code() Execution
Key Takeaways
Maintaining a clean codebase is an act of continuous discipline, not a one-time event. By integrating a dead code detection tool into your workflow, you ensure that your repository remains a lean, efficient environment for building new features.
- Prioritize maintenance: Dead code is technical debt that compounds over time.
- Automate the search: Use AST-based tools like repowise to detect unused code with high confidence.
- Prune incrementally: Use small, focused PRs to remove dead modules and exports.
- Leverage AI: Use MCP tools like
get_dead_code()to let AI agents handle the tedious work of unused code cleanup.
To see how repowise handles complex codebases, you can view the auto-generated docs for FastAPI or check out our live examples to see the platform in action.
FAQ
Q: Will removing dead code break my dynamic imports? A: It can if your tool isn't configured correctly. Always use tools that provide confidence scores and perform a manual sanity check on code that might be accessed via reflection or dynamic strings.
Q: Is dead code the same as commented-out code? A: No. Commented-out code should be caught by linters or simple searches. Dead code is valid, compilable code that simply isn't used by the application logic.
Q: How often should I run a dead code analysis? A: Ideally, once per sprint or as part of your CI pipeline. Keeping the "ghosts" out of your codebase requires regular vigilance.


