A codebase in production for four or five years is not just a program. It preserves decisions, pivots, feature launches, abandoned experiments, and the accumulated sediment of engineering done under time pressure. Reading a mature codebase this way resembles reading a city without knowing each neighborhood arose in a different era for a different purpose.
The dependency graph offers one of the best lenses for this reading. It does not identify dead code directly; it reveals the shape of the codebase's history. Modules whose connections follow distinctive patterns often preserve specific decisions from the past.
The Shape of an Old Feature
Modules remaining after features were removed or redirected often share a structural signature. Their efferent coupling is high, since they import shared utilities and core modules, while afferent coupling is low, since nothing in the current codebase imports them. They consume shared infrastructure without providing anything the current system needs.
A dependency graph cluster whose modules all point inward at the core, with no edges leading back from the core, likely represents a feature's ruins. The feature used shared utilities, but removing it did not remove its modules. Their core dependencies remained; only their dependents disappeared.
In the dependency visualization YAGNI generates, these clusters often look like disconnected peninsulas joined to the graph's main body by import edges. They reach inward, while nothing reaches back. Before call graph analysis begins, that shape is among the clearest available dead code signals.
The Shape of a Pivot
Product pivots produce another structural signature. They rarely create complete module isolation. More often, the old and new implementations share infrastructure. Both feature versions may use the same database and authentication system, and emit events through the same queue.
In the dependency graph, a pivot appears as two structurally similar module clusters. Both connect to the same core infrastructure and show similar sizes and internal organization, but only one connects to active entry points. The other is a shadow, present structurally and connected to shared resources, yet unreachable from any production path.
The dependency graph alone makes this pattern difficult to spot because the shadow cluster remains connected. Call graph analysis separates them: one cluster is reachable from active handlers, while the other is not. The dependency graph directs attention; the call graph identifies what to remove.
Reading Dependency Depth
Dependency depth is another useful archaeological signal: it counts the import layers between a module and the top-level entry points. Modules far from those points, at the graph's edge, and infrequently imported by nearer modules are good candidates for review.
This is not definitive evidence of dead code. A module can sit at the dependency graph's periphery while remaining important. Mathematical utility libraries, serialization helpers, and constants files are often distant from entry points by necessity. Dependency depth helps triage; it does not deliver a verdict.
Age makes the signal more diagnostic. In a codebase with steadily continuing active development, a module at the dependency periphery, rarely imported, and last modified two or three years ago is almost certainly dead or close to it. Structural isolation combined with time without modification is a strong signal.
The Utility Module Archaeology Problem
Utility modules are the most difficult archaeological subjects. A file named helpers.py or utils.ts often has a mixed history: some functions are ancient and still live, others came from features that no longer exist, some were speculative and never used, and some remain actively developed. The file is alive as a whole, but many functions are not.
Utility module history must be read at the function level rather than the file level. In a four-year-old codebase, git blame will show functions added during many periods by many engineers. Functions added in the first year with feature work later removed are likely dead. Functions modified in the last six months are probably live.
Call graph analysis is needed for certainty. Still, utility-function git history makes a useful pre-filter: when manually triaging functions, examine first those with old creation dates and no recent modifications in an otherwise actively-developed file.
What Archaeology Enables
Codebase archaeology is worth doing before automated analysis because it strengthens the human judgment layer in removal. When YAGNI surfaces a candidate, an engineer familiar with this reading can quickly tell whether it resembles expected residue from a past pivot or might remain live through a mechanism the analysis missed.
Experienced engineers assess removal proposals using the module's dependency-graph shape, age, author, and place in the codebase's history. Making that context explicit, instead of relying on institutional memory held only by the longest-tenured engineers, is part of good codebase maintenance.
Not every team has bandwidth for full archaeological analysis before a removal project. Even a brief look at dependency topology, focused on the peninsula and shadow-cluster patterns, can improve initial triage significantly. It shows where to look first.
Try YAGNI on your codebase. Free plan, no credit card required.
Get Early Access