AI Adoption GuideSoftwareRetire
Dead Code Detector
Instrumentation plus ML identifies code paths with zero production invocations over a configurable time window.
Software processDiscoverDesignBuildTestReleaseAdoptSupportRetire
By Don, DoneThat’s AI coach · updated
What a dead code detector does
A dead code detector combines production instrumentation with machine learning to find code paths that receive zero invocations over a configurable time window. It answers a practical question for tech leads: which functions, routes, feature flags, and modules are still in the tree but no longer run in real traffic?
Static analysis alone cannot settle that question. A symbol may look referenced in source while every call site is behind a killed flag, an obsolete client, or a branch that production never enters. Runtime evidence closes the gap. When invocation telemetry is present, the detector scores paths by observed activity, confidence, and risk signals. When that telemetry is missing, it returns empty output rather than guessing.
The model proposes candidates. Engineers still decide what to delete, how to stage the change, and when to ship. That human-in-the-loop boundary keeps the detector useful as a signal generator, not as an autonomous deleter.
Why unused paths stay expensive
Dead code is not free. It still compiles, ships, reviews, and confuses. Every unused path widens the surface that must be understood during incidents, security reviews, and migrations. Test suites keep covering branches that production never exercises. Onboarding slows because new engineers cannot tell which modules matter.
Cost shows up in several places at once. Build and deploy pipelines carry dead packages and unused assets. Observability dashboards list endpoints that never fire. Incident responders chase stack frames in code that has not run for months. Platform teams pay for capacity sized to a larger effective codebase than the live one.
Retirement stage work is where this debt becomes actionable. Once a product area, API version, or client cohort is gone, the remaining question is which paths are safe to remove without breaking the paths that still earn their keep. A detector that ties cleanup to measured zero-invocation windows gives tech leads a defensible queue instead of a gut-feel purge.
How instrumentation and ML work together
Instrumentation establishes the ground truth. Coverage may come from APM spans, request logs, feature-flag evaluation events, function-level probes, or custom call counters. The important property is consistent attribution: each observed invocation must map to a stable code identity such as a fully qualified function, route handler, or flag key.
The ML layer does more than sort by count equals zero. It learns which quiet periods are expected (seasonal jobs, admin tools, disaster-recovery paths) and which absences look like true abandonment. Features often include last-seen timestamp, traffic cohort, deployment correlation, related error rates, and co-occurrence with neighboring paths that are still hot. The output is a ranked candidate list with reasons, not a binary dump of every quiet symbol.
Configurability matters. A 7-day window may be enough for a high-churn web API. A billing batch job may need a full quarter before silence is meaningful. Tech leads set the window per service class so the detector matches operational reality instead of a one-size default.
Empty output is a first-class result. If a service lacks invocation telemetry, or if attribution cannot bind events to code identities, the detector should refuse to invent deadness. Silence from the tool is safer than a false positive that deletes a rare but critical path.
What tech leads should review before deletion
Treat every candidate as a hypothesis. The review checklist is short and deliberate:
- Confirm the identity: does the flagged path match the intended function, route, or flag in source control?
- Check the window: was the observation period long enough for that path’s expected cadence?
- Inspect exceptions: backup runners, support tooling, partner-only endpoints, and legally retained flows often look dead until they are not.
- Trace callers: static references, generated clients, and mobile app versions still in the wild can keep a path alive outside the instrumented surface.
- Plan the delete: prefer feature-flag kill switches, canary removals, and reversible PRs over large one-shot deletions.
Engineers own the delete step. The detector should never open a merge that removes code without human review. Pairing the ranked list with ownership metadata (team, service, last editor) keeps the queue actionable and reduces drive-by deletions in foreign modules.
Document the decision either way. Recording “kept because quarterly reconciliation” or “removed after 90 days of zero traffic” builds institutional memory and improves later model calibration without turning the process into ceremony.
How this fits adjacent retirement work
Dead path detection is one slice of retire-stage hygiene. After candidates are confirmed, related work often continues in two directions.
Cleanup agents can turn an approved list into concrete change sets: remove dead files, drop unused exports, and tighten public APIs. Usage analyzers for dependencies answer a parallel question at package boundaries: which libraries are imported but never exercised in production. Migration guides help when a path is not simply deleted but replaced, so callers move before the old surface disappears.
Keeping these concerns separate prevents muddled tooling. The detector’s job is evidence of non-use. Cleanup agents execute approved removals. Dependency analyzers focus on third-party and internal package waste. Migration generators explain the replacement path. Together they reduce cost without collapsing judgment into a single opaque automation.
Operating model and success signals
Start with one well-instrumented service and a conservative window. Publish a weekly candidate digest to owning teams, not a global dump that nobody owns. Require a reviewer sign-off before any removal lands on the default branch. Track false positives carefully; a few noisy flags will erode trust faster than a slow, precise queue.
Useful success signals are operational, not vanity counts. Look for shorter deploy artifacts, fewer unused routes in production catalogs, clearer ownership of remaining modules, and faster incident triage because responders spend less time in abandoned code. Measure review latency from candidate to decision so the queue does not rot.
Revisit windows when traffic patterns change. A product launch, a regional expansion, or a new partner integration can revive paths that looked dead under the previous cohort. Re-instrument after large refactors so identities stay stable; renamed functions without remapped telemetry recreate the empty-output case or, worse, misattribute silence.
The durable practice is modest: instrument what you run, learn what never runs, and let engineers delete with eyes open. Related reading: Codebase Cleanup Agent, Dependency Usage Analyzer, and Migration Guide Generator.
Is this worth automating for you?
Whether this pays back depends on how much time it takes your team today. Most teams estimate that from memory, and the estimate is usually wrong in one direction or the other. This one is rated high effort to implement, so the baseline matters more than usual.
DoneThat reconstructs where the time actually went, with no timers to forget, so you can measure the baseline before committing to a project and check the gain afterward.
Measure the baseline first