Be Software Architecture Wise
Be Wise Apps

Leer en español · Lire en français · Auf Deutsch lesen

Section 1.7 of 1.8 · Intermediate · 20 min read

Technical debt, architecture erosion and drift

Video lesson

4 min · narrated, with captions
Listen only (audio)
Read the transcript

Every team takes shortcuts, and every design ages as you learn more. This lesson explains technical debt: what it really means, how to see it, and how to decide what's worth repaying.

Imagine you move house in a hurry and throw everything into boxes without labels. You moved on time, which was the point. But now, every time you need something, you waste ten minutes searching. Those wasted minutes are interest. Unpacking and labelling properly is paying back the debt. A little of this is fine, and sometimes wise. The danger is letting it pile up.

Ward Cunningham coined the term in 1992. Shipping first-time code is like going into debt: a little speeds you up, as long as you pay it back promptly. He later explained that it was never a licence for sloppy code. His point was about understanding. You ship code that reflects what you know now, and as you learn more, you refactor it to match. That only works if the code is clean enough to refactor.

The financial words help. The principal is the effort needed to fix the problem. The interest is the extra effort you pay on every change that touches it. If a feature would take four days in clean code but takes six, the two extra days are interest. So debt in code that never changes costs nothing. Repaying is an investment decision: worth it when the interest you'll save is more than the principal.

Martin Fowler classifies debt on two axes: was it taken on deliberately or inadvertently, and was that reckless or prudent? Prudent and deliberate: we must ship now and deal with the consequences. It's conscious and planned, with a repayment plan, and it can be good business. Reckless and deliberate: we don't have time for design. The team knows better but cuts corners anyway, and usually slows itself down within weeks. Reckless and inadvertent: what's layering? The team can't see the debt it's creating. The cure is skills, review and mentoring, not refactoring alone. Prudent and inadvertent: now we know how we should have done it. Any team that learns ends up here, which is why continuous refactoring is normal work, not a sign of failure.

Code debt lives inside one module, and can often be repaid in hours or days. Architectural debt lives in the structure, such as services sharing a database. Repaying it touches many teams, and often data, so it's the most expensive kind. Perry and Wolf named two ways structure decays. Erosion comes from violations, such as a controller querying the database directly, bypassing the service layer. Drift comes from insensitivity: changes that break no rule but ignore the intended concepts, so the system gradually loses its shape.

How do you find the debt that matters? One technique plots each file by how often it changes and how complex it is. Complex files that rarely change cost little interest. Leave them alone for now. Simple files that change often are usually fine. But where complexity and frequent change coincide, you're paying interest on every commit. These hotspots are where repayment pays off first.

To make the case, an online shop measures the debt in a module that orders and payments share. It changed forty eight times in six months: the top hotspot. Extra effort on each change, plus incidents, comes to about fourteen developer-days a month. That's roughly eight thousand four hundred pounds. Separating orders from payments, in small steps, would cost about forty five developer-days, roughly twenty seven thousand pounds. With about eleven days saved each month, it pays back in around four months. Even doubling the cost, it pays back in about eight. That's a case in the sponsor's terms: days, money and payback, not code quality.

That's technical debt. The full section covers more ways to detect it, and how to manage it with a debt register, protected capacity and refactoring as you go. Next, in Section one point eight, we look at architecture across the lifecycle. See you there.

What you need to know

What Ward Cunningham actually meant

The term technical debt comes from Ward Cunningham's experience report on the WyCash portfolio management system, presented at the OOPSLA conference in 1992. He wrote that shipping first-time code is like going into debt: a little debt speeds development, as long as it is paid back promptly with a rewrite, and the danger comes when it is not repaid: Every minute spent on not-quite-right code counts as interest on that debt. Whole engineering organisations, he warned, can be brought to a standstill under the debt load.

The metaphor is often misread as permission to write sloppy code. Cunningham later explained (in a short 2009 video) that this was never his meaning. His point was about understanding. A team ships code that reflects its current, partial understanding of a problem, because that is the fastest way to learn from real use. As understanding improves, the code no longer matches what the team now knows, and every change has to work around the mismatch. Repaying the debt means refactoring the code so that it reflects what has been learnt. That only works if the code was written cleanly enough to be refactored in the first place. On this reading, debt is a by-product of learning, not of carelessness.

Since then, usage has broadened. Today "technical debt" covers any internal-quality shortfall that makes future change slower or riskier, whether it arose from learning, deliberate shortcuts, ignorance or plain neglect. The broader meaning is useful as long as you remember that the different kinds call for different responses.

Principal and interest

The financial vocabulary is what makes the metaphor powerful in conversations with non-engineers. The principal is the effort needed to fix the problem: to restructure the module, separate the database, remove the cycle. The interest is the extra effort paid on every piece of work that touches the problem while it remains. Martin Fowler's illustration: if a feature would take four days in clean code but takes six because of cruft, the two extra days are interest.

Three consequences follow. First, debt that is never touched costs nothing: a messy module that nobody changes accrues no interest, so repaying it may be wasted effort. Second, interest compounds: workarounds become foundations for more workarounds, new developers copy the patterns they see, and the cost of the eventual repair rises. Third, repayment is an investment decision: it is worth paying the principal when the interest you will save over a sensible horizon exceeds it. That turns an emotional argument ("the code is a mess") into an economic one ("this module costs us eleven days a quarter").

Fowler's technical debt quadrant

Not all debt is equal. In 2009 Martin Fowler proposed classifying debt along two axes: whether it was taken on deliberately or inadvertently, and whether the decision was prudent or reckless. The four combinations call for different responses.

Prudent and deliberate: the team knows the better design, judges that shipping now is worth the cost, and plans to repay. This is Cunningham's original sense, and it can be good business: a start-up proving demand before investing in scale. Reckless and deliberate: the team knows better but skips design because it believes it has no time, usually slowing itself down within weeks. Reckless and inadvertent: the team does not know enough to see that it is creating debt; the cure is skills, review and mentoring rather than refactoring alone. Prudent and inadvertent: a skilled team does good work and only later understands how it should have been structured. Fowler argues this is unavoidable for any team that learns, which is why continuous refactoring is part of normal work rather than a sign of failure.

The technical debt quadrant

Martin Fowler's technical debt quadrant: reckless or prudent, deliberate or inadvertentRecklessPrudentDeliberateInadvertent“We don’t have timefor design.”Knows better,cuts corners anyway“We must ship now anddeal with the consequences.”Conscious, planned,with a repayment plan“What’s layering?”Lacks the knowledgeto see the debt“Now we know how weshould have done it.”Learning revealsa better design
After Martin Fowler (2009): the two reckless quadrants are problems of discipline or skill; the two prudent ones are normal consequences of delivering under uncertainty.

Architectural debt versus code debt

Debt exists at every level, but its cost profile changes with scale. Code debt lives inside a module: duplicated logic, a function that does five things, missing tests, poor names. It is real, but it is local; one developer can usually repay it in hours or days, often while working on a nearby feature. Architectural debt lives in the structure: services that share a database so neither can change its schema alone, a domain split along the wrong lines so every feature touches four teams, a synchronous call chain that makes one slow service take down the checkout, a framework welded into every layer. Repaying it touches many modules, often many teams, and sometimes data.

Research supports the intuition that structural debt is the expensive kind. A 2015 survey of 1,831 engineers and architects by Neil Ernst and colleagues at the Software Engineering Institute found that architectural decisions were the most important source of technical debt reported, ahead of code-level issues. Tools that flag code smells are good at finding code debt and poor at finding architectural debt, because the problem is in relationships between parts rather than in any one file.

Architectural debt matters for the same reason architecture matters at all: as Why architecture matters showed, structural decisions are the ones with the highest cost of change.

Erosion and drift

Architecture debt often accumulates not through one bad decision but through thousands of small changes. Dewayne Perry and Alexander Wolf, in their 1992 paper "Foundations for the Study of Software Architecture", named two ways this happens. Architecture erosion is caused by violations of the architecture: a controller that queries the database directly, bypassing the service layer; a module that reaches into another's internal tables. Each violation makes the system more brittle, like removing a load-bearing wall. Architecture drift is caused by insensitivity to the architecture: changes that break no explicit rule but ignore the intended concepts, so the system gradually loses coherence and clarity of form. Drift is less immediately dangerous than erosion, but it obscures the architecture, which in turn makes violations easier.

Later researchers often use "erosion" for the broader gap between the intended architecture (what the documents and the architect's head say) and the implemented architecture (what the code actually does). Whatever the terms, the causes are familiar:

Deadline pressure that rewards the shortest path over the right path. Knowledge loss when the people who understood the design leave and new staff copy whatever they find. Invisible architecture: rules that live only in a wiki nobody reads, with nothing in the build to enforce them. Changed requirements, which leave a once-good structure fitting the problem badly. Organisational change: by Conway's law, a reorganisation creates pressure for the system to reshape itself along new team lines. Ageing technology: frameworks and platforms that fall out of support turn into debt without anyone touching the code.

Detecting debt

Debt you cannot see cannot be managed, and gut feeling is unreliable: developers tend to complain loudest about the code they touch most, which is useful, but they rarely know how much each problem costs. Several techniques make debt visible.

Hotspots. Adam Tornhill's Your Code as a Crime Scene (2015; second edition 2024) applies forensic ideas to version-control history. A hotspot is code that is both complex and changed often. Complexity alone is not enough: a complex file nobody touches costs no interest. Change frequency alone is not enough: a simple configuration file can change daily without harm. Where the two coincide, debt is being paid on every commit, and that is where repayment pays off first. In most codebases a small fraction of files accounts for most of the change activity.

Change coupling. Files or services that keep changing in the same commits are coupled, whether or not the code shows a dependency. If every change to the pricing service is accompanied by a change to the invoicing service, they are one thing pretending to be two. Change coupling across team or service boundaries is a strong sign of architectural debt.

Dependency cycles and rule violations. Static analysis can draw the real dependency graph and compare it with the intended one. Cycles (A uses B, B uses C, C uses A) mean none of the three can be understood, tested or released alone. Architecture rules ("the domain layer must not depend on the web layer") can be checked automatically on every build; these are fitness functions, covered in Chapter 11.

Delivery trends. Rising lead time for changes, more defects escaping per release, longer onboarding, more coordination meetings per feature and growing reluctance to touch certain areas are lagging indicators of accumulated interest. The DORA delivery metrics (such as change lead time and change fail rate) give a consistent way to track some of these over time.

Finding hotspots

A scatter plot of files by change frequency and complexity, with hotspots in the top-right cornerChange frequency (commits in the last 12 months)HighLowComplexityHotspots: start hereComplex but stable:leave alone for nowSimple and busy:usually fine
Plotting each file by how often it changes and how complex it is shows where debt is being paid most often: the few files in the top-right corner.

Managing debt

No healthy codebase has zero debt, and trying to reach zero is itself a waste. The goal is to keep debt visible, deliberate and affordable. Teams that do this well combine several practices.

A debt register. Record significant debt items where the team plans its work: what the problem is, where it is, how it arose, the estimated principal, the estimated interest (with evidence such as hotspot data or recent tickets), and the trigger for repaying it. A register turns vague complaints into a list that can be prioritised alongside features.

A fixed share of capacity. Many teams reserve a steady proportion of each iteration, commonly somewhere between 10 and 20 per cent, for debt repayment and other improvement work. There is no evidence for a magic number; what matters is that the share is protected and does not disappear whenever a deadline looms.

Refactoring alongside features. The cheapest time to repay debt is when you are already working in the area, because you have the context loaded and the tests running. Fowler calls this opportunistic refactoring. Its everyday form is the boy scout rule, popularised by Robert C. Martin: leave the code a little cleaner than you found it. Because hotspots are by definition where features land, this naturally concentrates effort where interest is highest.

Planned structural repayment. Architectural debt rarely yields to small steps alone. It needs an explicit, funded piece of work with a goal, a plan for migrating in increments, and the support of stakeholders. Techniques such as the strangler fig pattern (Chapter 11, Modernising legacy systems) let a team replace a structure piece by piece without a big-bang rewrite.

Prevention. Automated architecture checks in the build, code review that looks at structure as well as style, and up-to-date decision records slow erosion and drift at source.

Explaining debt to non-technical stakeholders

Sponsors and product owners do not need to hear about cyclic dependencies; they need to understand the effect on what they care about: speed, cost, risk and customer experience. The financial metaphor is a gift here, provided you use it with numbers.

Talk in time and money: "Every change to checkout costs us about three extra developer-days because of how payment and orders are tangled together." Talk in risk: "Two of our last four incidents started in this module." Talk in options: "Until we separate these, we cannot offer the new payment methods sales has asked for." And present repayment as an investment with a payback period, not as a plea for engineering time. Avoid two traps: presenting debt as a moral failing of the people who came before (it is usually prudent or inadvertent), and asking for an open-ended "refactoring sprint" with no measurable outcome.

Worked example

Worked example: estimating the interest on a debt

An online shop's order and payment logic share one large module and the same database tables. The team suspects this costs them a lot. They gather evidence from the last six months of tickets and version-control history, then estimate.

Changes touching the module48 changes in six months, about 8 per month (it is the top hotspot).
Extra effort per changeComparing similar changes elsewhere, developers estimate about 1.5 extra days each for understanding, working around the shared tables and extra regression testing.
Effort interest8 changes × 1.5 days = 12 developer-days per month.
Defect interest3 production incidents in six months traced to this module, each costing about 4 developer-days to investigate and fix: 12 days ÷ 6 = 2 days per month.
Total interestAbout 14 developer-days per month. At a loaded cost of £600 per developer-day, roughly £8,400 per month, or about £100,000 per year.
PrincipalSeparating orders from payments behind an interface and splitting the tables, done incrementally: estimated 45 developer-days, about £27,000.
Residual interest after repaymentDevelopers expect some overhead to remain: estimated 3 developer-days per month.
PaybackMonthly saving about 11 developer-days. 45 ÷ 11 is about 4 months to recover the principal.
RecommendationRepay, in increments of about two weeks each, starting with the tables most often involved in incidents. Track lead time for changes to checkout to confirm the saving.

Every number is an estimate with wide error bars. Doubling the principal to 90 days still gives payback in about eight months, so the conclusion is robust; that check is worth showing stakeholders.

The same method can show that some debt is not worth repaying: a messy module changed twice a year may have a payback period of several years.

The case is made in the sponsor's terms (days, money, incidents, payback), not in terms of code quality.

Check your understanding

Why can a very messy module be a low priority for repayment?

Because interest is paid only when the code is changed or causes failures. If nobody touches it and it is stable, the debt costs almost nothing.

Why is reckless, inadvertent debt not cured by refactoring alone?

Because the team did not know it was creating debt; unless its skills and review practices improve, it will keep creating the same kind of debt as fast as it repays it.

What is the difference between erosion and drift in Perry and Wolf's terms?

Erosion results from violating the architecture's rules; drift results from changes made without regard to the architecture that break no explicit rule but blur its concepts.

Why do hotspot analyses combine change frequency with complexity rather than use either alone?

Complex code that never changes accrues little interest, and simple code that changes often is cheap to change; only the combination shows where debt is costing effort repeatedly.

Next · 1.8Architecture in the lifecycleBig design up front versus emergent design, what the Agile Manifesto really says, Fairbanks' risk-driven model, intentional architecture and the runway, continuous architecture, walking skeletons, spikes, and architecture in DevOps.Continue →

Sources and further reading

Have a question about this section? ArchitectBot, the course’s AI study assistant, answers from the course first.

Ask: “How do I persuade my product owner to spend time paying down architectural debt when there is a long feature backlog?”