# Dead Code

> Variables, parameters, branches, methods or classes that nothing uses still sit in the codebase, costing reading time and misleading readers.

- kind: smell
- category: dispensables
- aliases: Unused code, Unreachable code
- canonical: https://aipatterns.dev/refactoring/smells/dead-code
- inspired-by: https://refactoring.guru/smells/dead-code

## Signs

- The editor or linter greys out a variable, import or private method.
- A function has no callers in the repository.
- A branch can never run: a condition on a feature flag removed a year ago, or a `case` for an enum value that no longer exists.
- A parameter is passed by every caller and ignored by the function.
- Large blocks are commented out "in case we need it".

## Why it happens

Requirements change and the old path is left in place because removing it feels risky. Feature flags are rolled out to 100% and never cleaned up. Refactorings leave orphaned helpers. Commented-out code is kept because people do not trust version control to remember it.

## Example

A checkout service still carries a legacy payment path and an unused parameter.

```ts title="checkout.ts"
import { legacyGateway } from "./legacy-gateway"; // only used below
import { stripe } from "./stripe";

const USE_LEGACY_GATEWAY = false; // rolled out in 2023

export async function charge(orderId: string, cents: number, currency: string, retries: number) {
  // const fraud = await fraudCheck(orderId);
  // if (fraud.score > 0.8) throw new Error("fraud");
  if (USE_LEGACY_GATEWAY) {
    return legacyGateway.charge(orderId, cents);
  }
  return stripe.charge({ orderId, amount: cents, currency });
}

function formatLegacyReceipt(orderId: string): string {
  return `LEGACY-${orderId}`;
}
```

`retries` is ignored, the flag is constant, the receipt helper has no callers, and the fraud check is a comment. After cleanup:

```ts title="checkout.ts"
import { stripe } from "./stripe";

export async function charge(orderId: string, cents: number, currency: string) {
  return stripe.charge({ orderId, amount: cents, currency });
}
```

Git history keeps the old code if anyone needs it.

## Treatment

- Delete it. Version control is the archive.
- If a parameter is unused, use [Remove Parameter](https://aipatterns.dev/refactoring/techniques/remove-parameter.md) and update every caller.
- If a class became unused or nearly empty, delete it, or use [Inline Class](https://aipatterns.dev/refactoring/techniques/inline-class.md) if a fragment is still needed.
- If a subclass is no longer instantiated, use [Collapse Hierarchy](https://aipatterns.dev/refactoring/techniques/collapse-hierarchy.md).
- For flag-guarded code, remove the flag and the losing branch together.
- Use the compiler, linter (`noUnusedLocals`, `noUnusedParameters`), coverage reports and "find references" to locate candidates; confirm before deleting.

## Payoff

- Less code to read, search, compile and keep secure.
- Readers stop wondering whether a path is live.
- Upgrades and refactorings no longer have to carry dead dependencies along.

## When to ignore

- The symbol is part of a published library API. Removing it is a breaking change; deprecate it first and follow your versioning policy.
- It is called by reflection, dependency injection, a framework by name, a scheduled job, a CLI, or another repository. "No references" in one repo does not prove "no callers".
- The code is behind a flag that is still live in some environment or for some tenant.

## Agent checklist

- [ ] You searched for callers by name across the repo, including string references, config, routes, DI registrations and templates.
- [ ] The symbol is not exported from a published package, or you deprecated it instead of deleting it.
- [ ] Flag-guarded code was removed only after confirming the flag's value in every environment.
- [ ] Unused imports and dependencies freed by the deletion were removed too.
- [ ] The build, type check and full test suite pass after deletion.
