Software Development

Technical Debt Has a Direction: Why Paying It Down in the Wrong Order Makes Things Worse

The standard advice is to “pay down technical debt.” Rarely discussed is that debt has topology — and tackling the wrong component first can actively spread instability through a codebase.

Ward Cunningham coined the technical debt metaphor in 1992 to explain something specific: that shipping first-time code is like borrowing money. You gain speed now and pay interest later, in the form of harder changes, slower onboarding, and brittle integrations. The metaphor was clear and practical, and it became the standard vocabulary of engineering leadership for three decades.

But like most financial metaphors applied to software, it has a blind spot. In financial debt, it doesn’t much matter which loan you pay off first — you’re always reducing total liability. In software, the order you tackle debt in matters enormously. Pay down the wrong component first and you don’t reduce liability. You increase it.

The reason comes down to structure. Codebases are not flat. They are graphs — directed dependency graphs, where some nodes are depended on by nearly everything and others sit at the edge touching almost nothing. A component at the center of that graph with unresolved debt is a fundamentally different kind of problem than a component at the periphery with the same amount of debt. Treating them identically is the mistake.

1. The Topology Problem No One Talks About

In graph theory, fan-in is the number of components that depend on a given node. Fan-out is the number of components a node depends on. Most engineers have intuitions about both: high fan-out suggests a component is doing too much, reaching into everything; high fan-in suggests it is heavily relied upon.

For technical debt triage, fan-in is the more dangerous dimension. A component with high fan-in is a dependency of many other components. If you change it — even to improve it — you trigger cascading change verification requirements across every dependent. If your test coverage of those dependents is incomplete, regressions hide. If your test coverage is non-existent, regressions are guaranteed, and you won’t find them until production.

Research presented at ICSA 2025 confirms this dynamic empirically. Studying architectural technical debt across multiple production codebases, researchers found that after debt repayment, FAN-IN increased by an average of 57.5% and FAN-OUT by 26.7% — meaning that resolving debt tends to centralize the architecture, making the repaired components more connected than they were before. Refactoring doesn’t just improve a module; it shifts the dependency gravity of the entire codebase toward it.

Resolving architectural technical debt improves software quality in the short term but can make the architecture more complex by centralizing dependencies.— ICSA 2025: “Tracing the Lifecycle of Architecture Technical Debt in Software Systems”

This is not an argument against paying down debt. It is an argument for understanding what kind of debt you are paying down before you start — and what safety structures need to be in place first.

The debt topology quadrant. Zone and arrow placement shows the correct triage order — always move from low fan-in toward high fan-in, building test coverage as you go.

2. What High Fan-In Debt Actually Looks Like

In most production Java codebases, the high fan-in components are not the ones that look complicated. They are the ones that look unremarkable. Utility classes. Base service abstractions. A SecurityContext holder. A shared DateUtils. A domain model root aggregate. These components were written early, proved useful, and became load-bearing walls that the rest of the building leans against.

What makes them dangerous to refactor isn’t their complexity — it’s their reach. A method signature change in a utility class used by 40 service classes is not a one-component change. It is a 41-component change. Each of those 40 callers must be verified. If you don’t have test coverage proving the expected behavior before you start, you have no way to confirm, short of testing everything manually, that you’ve preserved it after you finish.

Research on refactoring fault detection makes this concrete: in most cases, the absence of a test case calling the method being changed significantly increases the chance of missing introduced faults. Furthermore, nearly 80% of changes that break client applications are API-level refactoring edits — a striking figure that explains why high fan-in components carry such disproportionate risk when they are changed without adequate coverage.

The most common failure modeA team correctly identifies a core service class as the source of significant technical debt. They schedule a refactoring sprint. They start work before writing characterization tests. The refactoring introduces a subtle change in null-handling behavior. Twelve downstream callers are affected. Seven are caught by existing tests. Five are not. Those five ship to production.

This is not a hypothetical. It is the pattern behind a significant share of production incidents that are attributed to “a bad refactoring.” The refactoring itself was often technically correct. The failure was in the order of operations: changing before testing, on a component with a wide blast radius.

3. The Quadrant Framework: Triaging Debt by Risk Direction

The framework is built on two axes — debt level and fan-in — producing four quadrants that prescribe different responses. The key insight is that the correct action is not the same in all quadrants, and the common mistake is applying the same playbook everywhere.

Finding where your components live

The practical question is: how do you know which quadrant a given component belongs to? For fan-in, several tools make this straightforward. In Java, ArchUnit can query dependency graphs programmatically. Static analysis tools like SonarQube report afferent coupling (the fan-in metric) per class. For Maven projects, mvn dependency:analyze surfaces unused and undeclared dependencies, and the Structure101 toolchain visualizes your dependency graph with fan-in overlays.

For debt level, the same static analysis tools provide cyclomatic complexity, code duplication, and code smell counts. The combination — fan-in from dependency analysis, debt level from static analysis — gives you enough to place each component on the quadrant map with confidence.

SignalToolWhat to look forDanger threshold
Fan-in (afferent coupling)ArchUnit, SonarQube, Structure101Classes or packages depended on by many others> 10 direct dependents
Cyclomatic complexitySonarQube, Checkstyle, PMDMethods with many branch paths> 15 per method
Test coverage on dependentsJaCoCo, CoberturaLine/branch coverage of classes that call this component< 70% branch coverage
Change frequencyGit log analysis, CodeSceneFiles modified most often in recent history> 20 changes / 6 months
Bug concentrationJira/GitHub issues mapped to filesBug reports pointing to the same module repeatedly> 3 incidents from one module

4. The Correct Sequence: From Periphery to Core

The sequencing principle is simple to state: always work from the outside of the dependency graph toward the center, and never touch a higher fan-in component until the components that depend on it are adequately tested. This ensures that when you do change a central component, you have a verification net that will catch regressions before they ship.

1. Map the graph first

Run dependency analysis and produce a fan-in ranking of your top 20 most-depended-on components. This is your risk map. Everything else is informed by it.

2. Audit test coverage on high fan-in components

Before touching anything central, measure branch coverage on all callers of high fan-in components. If coverage is below 70%, that is the first debt to pay — in the form of characterization tests, not refactoring.

3. Refactor leaf nodes first (Quadrant 1)

Work through low fan-in, high-debt components freely. This delivers real debt reduction with minimal risk, builds team habits, and is often where the most painful day-to-day friction lives.

4. Apply the Strangler Fig pattern to Quadrant 2

For high fan-in, high-debt components, never attempt a direct rewrite. Use the Strangler Fig pattern: build a clean interface around the old component, route new callers through the clean interface, and migrate existing callers one by one. The old component is decommissioned only when the last caller has moved.

5. Protect Quadrant 4 with architectural tests

Stable, high fan-in components with low debt are your most valuable asset. Protect them by codifying your architectural invariants using ArchUnit tests that run in CI and fail the build if coupling rules are violated.

The Strangler Fig in practiceCoined by Martin Fowler in 2004, the Strangler Fig pattern builds a new implementation around the legacy code until the old code can be safely removed — just as the tropical fig tree grows around its host and eventually replaces it. The critical discipline is that the old and new implementations run in parallel, verified against each other at every step. This eliminates the big-bang rewrite risk that has killed real projects — most infamously Target Canada, which attempted a full-cut-over inventory system replacement, opened stores with empty shelves, and was bankrupt within two years.

5. A Note on Change Frequency as a Risk Multiplier

Fan-in alone is not sufficient to assess risk. A high fan-in component that never changes is a different animal from a high fan-in component that is modified weekly. The combination of high fan-in and high change frequency is where the real danger lies — these are the components where errors compound fastest, because each change ripples to many dependents and the code is never stable long enough to accumulate a reliable test suite around it.

Tools like CodeScene and git log analysis surface this pattern by computing “hotspots” — the intersection of high churn (change frequency) and high complexity. A hotspot in a high fan-in module is an active fire, not a debt item to schedule. It deserves immediate attention in the form of tests, not immediate refactoring.

A module with high fan-in, high debt, and high churn is the most dangerous configuration in a codebase. Every change introduces regression risk, the blast radius is wide, and the rapid change rate makes it hard to build a stable test suite. The correct response is to first slow the churn — by reducing coupling so fewer requirements force changes to this component — before attempting to pay down the structural debt.

6. Applying the Framework: A Worked Example

Consider a mid-size Java monolith with the following four components flagged in a debt audit:

ComponentFan-inDebt levelChurn (6 mo)Caller coverageAction
UserAccountService34 classesHigh (CC: 28)High (61 changes)48%Tests first, then Strangler Fig
ReportingEmailJob2 classesHigh (CC: 22)Low (4 changes)82%Refactor now — safe leaf
PaymentGatewayAdapter8 classesLow (CC: 6)Low (7 changes)91%Guard with arch tests; no debt sprints
DateTimeUtils52 classesLow (CC: 4)Very low (1 change)88%Stable core — protect above all

The natural instinct is to tackle UserAccountService first — it has the highest debt score and is visibly causing friction. The framework says the opposite: start with ReportingEmailJob, which has high debt but a blast radius of two. Gain confidence. Build coverage habits. Then, with those habits established, write characterization tests for UserAccountService‘s 34 dependents before writing a single line of refactored code in the service itself.

Notice that DateTimeUtils, despite having the highest fan-in in the list, scores low on debt. That is Quadrant 4 — the stable core. The right action is not to improve it but to protect it: no new technical shortcuts, architectural tests enforcing its interface stability, and a review gate on any pull request that modifies it.

7. What We’ve Learned

Technical debt is not a flat list of items to work through in any order. It exists in a dependency topology, and the topology determines the risk of each refactoring effort. The central insight — that fan-in defines blast radius, and blast radius determines whether a refactoring effort reduces or spreads instability — is what the standard “pay down debt” advice leaves out.

The correct sequence is always from the periphery toward the core: start with isolated, low fan-in components to build test coverage and confidence, then work inward using the Strangler Fig pattern for high fan-in components that carry significant debt. Never begin a central refactoring before the callers of that component are adequately tested. And treat high fan-in, low-debt components as infrastructure to be protected, not improved.

The research from ICSA 2025 adds a sobering nuance: resolving architectural debt tends to increase centralization, making the repaired components more connected than before. That is not a reason to avoid debt reduction — it is a reason to approach it with full awareness of what changes, and to design the repayment sequence so that each step builds safety rather than assuming it.

Finally, the most dangerous configuration in any codebase is high fan-in, high debt, and high churn simultaneously. That is not a debt item. That is an active risk that deserves its own triage — beginning with slowing the churn through decoupling, then building coverage, and only then refactoring the internals with confidence.

8. Further Reading & References

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button