Skip to main content
Dependency Graphs in Practice: Problems Visible Before the Agent Runs
Back to blog

Dependency Graphs in Practice: Problems Visible Before the Agent Runs

A module can look ordinary in isolation while its dependency graph shows it embedded in a dense cluster. Before running automated analysis, I inspect the raw import relationship graph generated from the codebase, not a cleaned-up visualization. A healthy, well-maintained codebase has a recognizable shape; significant technical debt appears in the graph before call graph analysis.

Here is what I check and what each pattern usually means.

The Hairball Center

In a large, mature codebase, the graph's center often contains a module cluster so densely interconnected that it appears as a solid block instead of separate nodes and edges. In this region, everything imports from everything else, with edges pointing in every direction.

Engineers usually call this spaghetti code at the module level. It does not always mean dead code, but it almost always marks a zone where removal will be difficult. Any function removed from the hairball may have callers scattered among several modules in the cluster. Nothing is cleanly isolated; everything is coupled to everything.

In a hairball, first ask not "what can we remove" but "can we untangle this first." Removal is possible, but careful sequencing is needed: find modules in the cluster that are dead or close to dead, detach them from live modules, and then remove them. Detachment often takes the most work.

Stranded Clusters

Opposite the hairball center are stranded clusters: small groups of tightly-connected modules linked to the codebase's main body by only one or two import edges. Those edges point inward, with the cluster importing from the main body, not outward, since the main body does not import from the cluster.

A stranded cluster strongly indicates dead code. It is a self-contained mini-system built on shared infrastructure but no longer used by anything in the main system. Its imports from the core are enough for it to run; the lack of edges back from the core shows that it is no longer running.

These are usually the easiest places to begin removal. The cluster's modules depend on one another and on core utilities, but nothing depends on them. Remove them together without touching live code, except for cleaning up the import statements they added to the utility modules they used.

The Unusually High-Connectivity Utility Module

In a large, mature codebase, a module with import edges from 40, 60, or 80 other modules is not unusual. These catch-all utility modules accumulate functions over time because "this is a useful helper and helpers.py is where we put those." They are almost always partly dead: many callers actively use some functions, one or two things call others, and nothing calls some at all.

High connectivity alone does not signal dead code. In these modules, look for high connectivity combined with specific functions that have very low or zero connectivity. A function in a 70-importer module referenced only by modules in stranded clusters is dead. The highly connected module hides it from easy discovery, but the pattern appears when you inspect the function level instead of the file level.

For this reason, YAGNI's dependency visualization displays afferent coupling per function, not per module. The module view conceals that 35 of its 40 functions have no afferent coupling at all.

Long Import Chains

Tracing an import chain from an entry point to a deeply-nested utility function shows how long the code has accumulated layers. A five-hop chain, entry point imports service imports repository imports model imports validator imports helper, is not unusual. A twelve-hop chain suggests layers accumulated without anyone removing those no longer serving their original purpose.

Long import chains are not directly a dead code signal, but they make dead code difficult to see. Engineers at the top of the chain do not inspect eight layers below to see what the bottom utilities do. Dead code in deeply-nested utility layers can survive for years because no one has reason to examine it.

When analyzing codebases with long import chains, we give extra attention to modules at the bottom: the deepest utilities and the helpers of the helpers. They are most likely to contain functions written for code that no longer exists three or four layers above.

What the Graph Does Not Tell You

The dependency graph shows import relationships, not call relationships. A module imported by an active part of the codebase may have no function actually called, yet the graph will not flag it. It still looks connected. Call graph analysis is needed to find the functions inside that connected module with no callers.

The graph also says nothing about when activity last occurred. A stranded cluster may have been stranded for three years, or for three weeks because a feature is held in a branch and its module entered the main codebase before the feature was ready. Stranded shape plus git blame age is more reliable than stranded shape alone.

Use the dependency graph for triage: it identifies areas most likely to contain removable dead code, narrows the surface for call graph analysis, and shows which areas should be easy to clean up versus which need careful sequencing. Call graph analysis then verifies which specific functions in those areas are actually dead.

Try YAGNI on your codebase. Free plan, no credit card required.

Get Early Access

More from the blog

View all articles