Glossary · Structure and dead code

What is dead code?

Dead code is code that can never run, or that nothing in the project uses. Unreachable code sits where no execution path leads, such as statements after a return. Unused code is a file, export or function that nothing imports or calls. Both add maintenance cost and no behavior, and both can usually be deleted.

Last reviewed: November 2026

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:

KindExampleHow it is found
Unreachable codeStatements after return, if (false)Control-flow analysis inside one function
Unused codeA file nothing imports, an export nothing uses, a function nothing callsCross-reference against the import and call graph
Unused dependencyA 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 knip reports 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 from main reaches. staticcheck reports unused unexported identifiers as check U1000.
  • Rust: the dead_code lint runs on every cargo build; cargo machete finds 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.

TierConfidenceMeaning
High0.7 and aboveNo references found
Medium0.4 to 0.7Review first
Lowbelow 0.4Hidden 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-internals and --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.

FREQUENTLY ASKED

Dead code: questions

How do you identify dead code?

Start from the entry points (main functions, routes, exported APIs, tests) and follow imports and calls. Whatever the traversal never reaches is a candidate. Use the tool for your language, then check each candidate for dynamic use such as reflection, config-driven loading or string-built imports.

How do you remove dead code safely?

Delete in small, single-purpose pull requests, strongest evidence first, and run the full test suite. Treat whole files as a review queue: search build files, manifests and config for the path before removing one, because loaders often read files by name.

What is the difference between dead code and unreachable code?

Unreachable code is a subset of dead code: it can never execute, for example after a return or behind a condition that is always false. Dead code also covers code that could run in principle but that nothing calls or imports, such as an exported function with no callers.

Does the compiler remove dead code?

Often, but only inside a compiled artifact. Optimizers and bundlers drop code they can prove is never used (tree shaking, dead-code elimination), which shrinks the binary. The unused source stays in your repository, still read, reviewed and maintained.

See it on your own repository.

Index a repository free and read the result for your own code and history. Rolling it out to a team? We will walk you through it.