DEV Community

Hassan Amini
Hassan Amini

Posted on Originally published at hassanaminidev.ir AI-assisted

Can I Delete This Code? A Practical Dead Code Audit with AI

Can I Delete This Code? Find Dead Code with AI

You search for a component. There are imports. There are tests. Nothing looks broken.

Then you follow the imports one level further and realize nobody can reach the feature from the application.

So, can you delete it?

Maybe. Or you have just found a feature that was built correctly and never connected to the product.

That distinction matters when cleaning up a real repository. This guide walks through an evidence-based audit, using a fictional store campaign as the running example. It also shows how AI can speed up the investigation without turning guesses into deletion instructions.

The short version: find candidates, trace their execution roots, check product intent and external contracts, then make one small change and validate it.

What counts as unused code?

Dead code might be an unreachable branch, a function without callers, or a disconnected group of components, hooks, and services. Editor warnings, lint, and settings such as TypeScript's noUnusedLocals help with some local issues. They do not establish that every feature has a real consumer.

Ask which valid execution root reaches the code. That root might be a page, API, command, or worker. A migration need not run through the UI. A component does not become an active product feature just because a test imports it.

Deleting source also does not automatically improve speed. The bundler may already exclude it. Tree shaking concerns build output. Reducing maintenance ambiguity is valuable independently; performance claims need separate measurements.

1. Define the scope and capture a baseline

Start with an old feature or a particular folder, rather than the entire repository. Record the commit, local changes, and investigation scope:

git status --short
git rev-parse HEAD
Enter fullscreen mode Exit fullscreen mode

Then run type checking, relevant tests, and the build through the project's existing scripts. Inspect their definitions in package.json. These initial results are your baseline: if a test fails after deletion, you can distinguish a new regression from an existing problem.

Record checks that already fail or cannot run. You need not repair the entire project before removing a helper. However, if an existing failure prevents you from evaluating affected behavior, establish another valid check or address that limitation first.

2. Find candidates and interpret the tool output

Search the suspected component name, exports, and file path. For an illustrative component called PromoBanner:

rg -n 'PromoBanner' src tests scripts
Enter fullscreen mode Exit fullscreen mode

Replace the folders with those in your project. Read the matches rather than counting them. A name may appear in a comment, test, or central export. Aliases and indirect loading can escape a simple search.

For JavaScript and TypeScript, tools such as Knip can report suspicious files, exports, and dependencies. In a project where it is installed, run analysis with pnpm exec knip. Consult the official getting started guide for setup.

Before relying on the report, identify application entries, workers, commands, and public package interfaces. Do not mark every file as an entry merely to suppress warnings, or ignore something you have not investigated. Initial output is an investigation list; automatic bulk removal is premature.

3. Trace references to actual execution

For a component, ask who imports it, whether it renders in JSX, where its parent is mounted, and whether that page is reachable. Do not stop at the first import.

For a function, trace the caller back to a root. The caller may itself lack consumers. A re-export in index.ts is not sufficient evidence; find what reads that export.

Check side effects too. Loading a module may perform registration or initialization without providing a value used later. An apparently unused import can still support behavior. Public packages also require consideration of consumers outside the repository.

Reachability does not mean execution on every request. A dialog may require a click; an administrative report may require a particular role. An absence of runtime observations does not automatically establish the absence of a valid path.

4. Check indirect execution paths

A route may be discovered by filename, a command started through a package script, or a job launched by CI or cron. When conventional imports are absent, inspect configuration, workflows, and deployment.

Dynamic loading matters too. A module name may come from configuration, or plugins may be discovered from a directory. That discovery contract is part of the consumption evidence.

For an endpoint, no requests from your frontend does not exclude another service as a consumer. If the external contract is unclear, record an unknown status and a specific follow-up question. “No consumer found in this repository” is a narrower claim than “no consumer exists.”

An example: a banner that appears to be used

Imagine a store that previously had a campaign page. Searching for PromoBanner reveals an export, a test, and a component called CampaignPanel. At first glance, the banner looks used.

Follow the chain: the panel uses a hook and campaign service, but the old route has been removed and no active page renders the panel. Connections inside the group remain. The group's connection to the application is missing. This is an isolated subgraph.

If the campaign has ended and no valid commitment remains, the group may be removable. If the page was missed during a redesign while the campaign remains necessary, restore its connection. The banner test cannot decide between these outcomes.

Do not treat downstream importers as proof that a group is active. Investigate its root and current product purpose. You may have found obsolete code, or a capability whose user entry point is incomplete.

5. Examine tests and background work separately

If only tests reach an implementation, mark it test-only and investigate why production usage is absent. Its behavior may be obsolete, or its wiring may be missing. Fixtures and mocks are different: they naturally have no production consumers.

For background workflows, inspect producer and consumer together. A scheduler may create jobs while its worker is not running in deployment. Deleting the worker can conceal the integration problem.

If the capability is required, restore the connection. If it is being retired, consider the producer, schedule, and existing work too. One quiet day does not prove a monthly workflow unused; observations must match its execution cycle.

6. Turn the finding into a clear decision

Simple statuses are enough, provided “no consumer found” is kept separate from “removal approved”:

Finding Next action
Removal candidate Final review, then a small deletion
Test-only Establish whether behavior is needed and why wiring is absent
Intentionally inactive Retain with reason, ownership, and a review point
Missing connection Complete the capability or retire the workflow
Generated output Review its generator and retention policy
Unknown Specify missing evidence; no deletion yet

Record the path, established consumers, open question, and proposed action. For the campaign, a useful note is: “Consumed inside the panel and a test; no active route found; external service usage remains unchecked; do not delete the panel alone.”

Explain confidence levels if you use them. Coverage of relevant paths matters more than the tool's certainty. Feature-flagged code and generated output need assessment against their own contracts.

7. Keep removal small and validation relevant

Once the decision is clear, limit the change. Avoid mixing unrelated refactoring into the deletion. Check shared dependencies: a helper used by active checkout must remain when an old campaign disappears.

Repeat baseline checks. Type checking helps expose broken dependencies, tests exercise covered behavior, and builds reveal some output and discovery problems. For a sensitive change, execute the affected user journey or background process as well.

A passing build does not validate an external consumer. Checks should address the relevant contract and environment. Explain in the pull request what was removed, why, and which evidence supports the result.

Using AI for dead code detection: investigation to change

AI can collect references, follow consumption chains, and organize findings. But “delete unused files” oversimplifies the task. Divide the work into three stages: investigation, decision, and a focused change.

Stage one: ask for evidence, not deletion

Give the assistant a defined scope and require inspectable file paths and references. A practical prompt is:

Review the campaign module for unused-code candidates. For each candidate, trace consumers to entry points; separate test and production usage; inspect scripts, registration, and dynamic loading. Record possible external consumers and unanswered questions. Do not modify files during this pass.
Enter fullscreen mode Exit fullscreen mode

This encourages a reviewable record instead of an authoritative deletion list. If the assistant says the panel is unused, you should be able to inspect which paths it checked and which remain unresolved.

Stage two: challenge the conclusions

Ask the assistant to critique its report: “For each proposed deletion, which execution path or external contract might the analysis have missed?” This can reveal new leads, but it is not independent confirmation. The same assistant can repeat its original assumption.

Verify important references yourself. Compare “no usage exists” with the actual investigation boundary, and do not convert missing evidence into certainty. Product decisions, such as whether a campaign should continue, must come from a valid project requirement.

Stage three: request one specific change

After the decision is established, constrain the next request: “Remove only the retired campaign group. Preserve the shared checkout helper. Avoid unrelated behavior changes. Report the diff and actual validation results; stop before deleting any unresolved item.”

The assistant should distinguish checks it executed from checks it did not run. “Tests should pass” is not a test result. Review the diff and compare before-and-after evidence. AI accelerates investigation; confidence comes from evidence and validation.

Keep the investigation useful when using AI

Give the assistant access to the relevant project context, including package scripts, framework configuration, and known deployment entries. A model that sees only one component cannot reliably judge the entire feature's reachability. If it cannot inspect a worker configuration or external contract, that limitation belongs in the report.

Request a compact evidence table: candidate, known consumers, entry path, test usage, unresolved question, and proposed action. For the campaign example, an acceptable row should expose the missing route and unresolved service consumer. A bare “unused” label leaves you with nothing to review.

Also separate proposed actions from completed work. An assistant may describe the correct validation commands without executing them, or suggest a deletion without producing the expected diff. Ask it to report actual changes and observed results. This keeps the workflow concrete whether you use an editor assistant, a terminal agent, or manual analysis.

Prevent the same situation from returning

When retiring a route or feature, inspect its components, hooks, tests, and services. Give inactive code an owner and a review criterion. “Maybe later” should not become an indefinite status.

Introduce analysis into CI gradually: establish configuration and resolve false positives before preventing new issues. Hundreds of unexplained failures often encourage disabling the tool rather than understanding the code.

Start with one feature

Choose an old capability, trace its consumers, and resolve the open questions. The outcome may be fewer files or a restored connection. Either way, you should be able to explain why that action makes sense.

Have you found a feature that had tests and internal imports but no reachable entry point? What helped you decide whether to remove it or reconnect it?


Originally published on my blog.

This article was adapted with AI assistance. The campaign scenario is illustrative.

Top comments (0)