Also called Unused code, Unreachable code, Orphaned code
In depth How to find dead code in your repository: the command for each language and a safe order for deleting what it finds.
How dead code is measured
Dead code has no single formula. Detectors ask a reachability question: from the project's entry points, which code can be reached by following imports, calls, inheritance and references? Code the traversal does not reach is a candidate.
Three kinds need different checks, so a useful report keeps them apart:
| Kind | Example | How it is found |
|---|---|---|
| Unreachable code | Statements after return, if (false) | Control-flow analysis inside one function |
| Unused code | A file nothing imports, an export nothing uses, a function nothing calls | Cross-reference against the import and call graph |
| Unused dependency | A package declared in a manifest that nothing imports (see zombie dependency) | Compare the manifest with the imports |
Compiler dead-code elimination is separate: it trims the output artifact, not your source tree.
Why dead code matters
Dead code is still read in review, searched, refactored and migrated. It keeps old APIs looking alive and can mislead coding agents into copying a stale function. It counts toward technical debt as maintenance cost with no return.
The risk in deleting it is false positives. Static analysis proves that nothing imports a symbol, not that nothing loads it, so Django settings strings, dependency-injection containers, plugin systems and reflection all produce candidates that are in fact live.
How to measure dead code yourself
Each ecosystem has a tool its community trusts:
- JavaScript and TypeScript:
npx knipreports unused files, exports and dependencies, with plugins for common frameworks. ts-prune still covers unused exports, but its README describes the project as resurrected after a long hiatus, so Knip is the usual pick for a new setup. - Python:
vulture src/ --min-confidence 80. Vulture gives each finding a confidence, with dynamic-use guesses at 60%. - Go:
deadcode ./...(golang.org/x/tools/cmd/deadcode) lists functions no execution frommainreaches.staticcheckreports unused unexported identifiers as check U1000. - Rust: the
dead_codelint runs on everycargo build;cargo machetefinds unused crates. - Java: IntelliJ inspections and PMD flag unused private members.
These are the right pick for a single-language repository. They do not rank findings by git history or cover a mixed repository in one report.
How repowise measures dead code
repowise computes dead code from the dependency graph it builds at index time, with git metadata for scoring and zero LLM calls. It reports four kinds: unreachable_file, unused_export, zombie_package and unused_internal (private symbols, with --include-internals). Entry points are framework-aware: decorators, route files, test fixtures and config-loaded files count as live, so a Next.js page or a Django view is not flagged.
Every finding gets a confidence from 0.0 to 1.0, and caps lower it when a runtime loader, a config-like path or a name written elsewhere in the repo suggests dynamic use.
| Tier | Confidence | Meaning |
|---|---|---|
| High | 0.7 and above | No references found |
| Medium | 0.4 to 0.7 | Review first |
| Low | below 0.4 | Hidden by default |
Deletion-ready is a strict bar, so agents can delete with confidence: an unused export at 0.7 or above with no runtime-load risk on its path. Whole files, zombie packages and internals arrive as ranked review findings.
- CLI:
repowise dead-code, with--safe-only,--kind,--min-confidence(default 0.4),--format table|json|md,--include-internalsand--no-include-zombie-packages. - MCP:
get_dead_code, one of the 10 repowise MCP tools, so an agent can fetch findings with evidence.
The internals are in how repowise dead code detection works.