Technical Debt vs Architectural Debt: A Triage Ledger

By Arkaitz

A missing index on one query and a shared database with eleven owning teams both get filed under technical debt. They behave nothing alike.

One is a loan you clear inside a sprint, priced in hours, owned by the team that took it. The other is a mortgage on the shape of the system, priced in other people's sprints, owned by nobody until it is owned by everybody. Put both on the same list and the cheap items win the argument while the expensive ones keep compounding.

So treat them as two instruments, not two severities. You get a ledger with five columns — interest rate, blast radius, reversibility, owner, and pay-down rule — plus the three traps that make technical debt conversations useless: moralising, rewriting, and counting TODOs. The decision method this sits inside is the system design practical guide; this post owns only the split between the two instruments and how you price them.

What is the difference between technical debt and architectural debt?

Code debt is a local loan with a short term. Architectural debt is a structural loan with a long one. The difference is not severity, and it is not which one hurts more on a bad day. It is scope, interest rate, owner, and the unit you repay in.

The two debts fail differently too. Code debt makes one file harder to change. Architectural debt makes a whole class of changes require a meeting, a migration, and three teams' calendars before anyone writes a line.

Code debt is local, and a sprint can clear it

Code debt lives inside a boundary that already exists. Duplicated validation, a 200-line function, a test that mocks everything, a missing index, a hard-coded tax rate. One team can fix it without asking permission, and one afternoon is often enough to make the interest disappear.

Martin Fowler's arithmetic is the cleanest way to price it. If a confusing module turns a four-day feature into a six-day feature, the two-day difference is the interest. Spending five days to clean the module costs you three days on this feature, breaks even by the third feature, and pays back on every feature after that. That is a calculation you can run inside a sprint, with one team's numbers.

This is also why early-stage code debt is usually cheap to carry. At ecommerce architecture at 100 users, an unindexed query and a hand-rolled session table are real debt, and the repayment is a migration plus an afternoon. The blast radius is one application, one team, one deploy.

Architectural debt is structural, and one team cannot clear it alone

Architectural debt lives in the seams between things: data ownership, module boundaries, contracts between services, and the coupling that decides how many teams must agree before a change ships. The Software Engineering Institute's architectural technical debt library defines it as a design or construction approach that's expedient in the short term, but that creates a technical context in which the same work requires architectural rework and costs more to do later than it would cost to do now (including increased cost over time).

Read the definition again and notice what it does not say. It does not say bad code. It says the same work costs more later.

A shared database with eleven owning teams is architectural debt because every schema change now needs eleven reviewers and a rollback plan. A synchronous call chain across five services is architectural debt because a p99 regression in the last hop becomes a product outage in the first.

The repayment unit is a quarter, not an afternoon, and the owner is a group: an architecture owner, a staff-plus engineer with a mandate, or a funded platform team. Nobody clears architectural debt on a Friday.

Where does the debt metaphor come from — and what did Cunningham actually mean?

The metaphor comes from Ward Cunningham's 1992 OOPSLA experience report on a financial product, and it was an argument for consolidation, not a licence for sloppiness. Cunningham wrote that shipping first-time code is like going into debt: A little debt speeds development so long as it is paid back promptly with a rewrite. He then added the sentence most debt dashboards forget: Every minute spent on not-quite-right code counts as interest on that debt (The WyCash Portfolio Management System).

Two things follow, and both matter for triage. I would not read the report as permission to accumulate debt, because Cunningham's own addendum frames the metaphor as a defence of refactoring rather than an excuse to skip it.

First, the original debt was about immature code that had not yet been consolidated. The payment was refactoring, and the danger was an organisation brought to a stand-still under the debt load of an unconsolidated implementation. The metaphor described a normal, healthy phase of incremental development. It did not describe a moral failure, and it did not describe a backlog of sins.

Second, interest is not a metaphor for we should clean this up eventually. Fowler defines it precisely: The extra effort that it takes to add new features is the interest paid on the debt (Technical Debt). If nothing is being added, no interest is being paid. That single distinction kills most bad debt arguments, because a terrifying module nobody touches is not costing you anything this quarter.

Why is architectural debt more expensive than code debt?

Because architectural interest is charged to every team that touches the system, not just the team that took the loan. Code debt taxes one team's velocity. Architectural debt taxes the organisation's ability to change its mind.

That difference shows up in the research. A survey of 1,831 software engineers and architects across three large organisations found that architectural decisions are the most important source of technical debt (Measure It? Manage It? Ignore It?).

The practitioners were not complaining about long functions. They were complaining about the decisions that set the shape everything else was built on.

The failure mode has a name. A separate SEI study of a safety-critical system found that the expensive rework came from missed architectural dependencies rather than from any single bad implementation. The code was not the problem. The map of what depended on what was wrong, and it stayed wrong until a major change exposed it.

Interest is charged to people who never took the loan

Compounding is the mechanism, and it is easy to see once you look for it. A boundary that should have existed in year one does not appear in year two. Instead, three teams route around it, each with a slightly different workaround.

By year three, the workaround is the interface, and the original decision is invisible. Every new feature now pays interest to a loan taken before half the team joined.

Shopify described the pattern honestly in Deconstructing the Monolith. The codebase had grown for over a decade at the hands of more than a thousand developers, and by 2016 the tripwires were no longer stylistic.

Making a seemingly innocuous change could trigger a cascade of unrelated test failures. New engineers had to understand orders, payments, and shipping before they could ship a feature in shipping. That is architectural interest, charged to every team, every week.

Google's version of the same problem is described in Large-Scale Changes. With a codebase of more than two billion lines in a single repository (Build Systems and Build Philosophy), the company cannot rely on individual teams to migrate themselves. It funds central migration teams, because, in the book's phrase, nobody likes unfunded mandates, and because organic migrations are unlikely to succeed when engineers copy existing code as their example. It also names the failure mode architectural debt creates: a haunted graveyard, a system so old and fragile that nobody dares enter it, blocking every migration that touches it.

The cost lands as delivery drag, not as a bigger bill

Architectural debt rarely sends an invoice. It sends a delay. A feature that should take two days takes nine because it needs a data migration, a compatibility window, and sign-off from two other teams. Nobody can point at the line item that ate the seven days, so nobody escalates it.

That is what delivery drag means in practice: work that is planned, staffed, and understood, but cannot start because a prerequisite crosses three teams. Code debt shows up as friction inside a task. Architectural debt shows up as a task that cannot be scheduled.

Which debts can you reverse, and which close the door behind you?

Reversibility is the column most teams skip and the one that should set urgency. A debt you can undo next month is a scheduling problem. A debt that closes a door is a commitment, and commitments deserve a different decision process.

Amazon's 2015 shareholder letter makes the distinction better than most architecture decks. Some decisions are consequential and irreversible or nearly irreversible – one-way doors, which Amazon calls Type 1. Most decisions are changeable, reversible – they're two-way doors, Type 2. The letter also names the organisational failure: As organizations get larger, there seems to be a tendency to use the heavy-weight Type 1 decision-making process on most decisions, including many Type 2 decisions (2015 letter to shareholders).

Apply the door test to a debt item with three questions. How many teams must agree before you can undo it? How much data must move? Can the old and new path run at the same time?

I would put reversibility above interest rate for anything that touches stored data, because the interest can always be paid later and the door cannot always be reopened.

A missing index answers all three in the reversible direction: one team, no data movement, and you can revert the migration. Choosing a partition key for a system that will hold five years of events passes all three: many teams, a data move, and no parallel path without building one. The same test separates a cache you can delete from a public API contract you cannot. If you are about to make the second kind of decision, the mechanics are in how to design a distributed system, and they are worth reading before the schema is frozen rather than after.

A triage ledger for technical debt vs architectural debt

Score five columns, then let the score decide who pays and when. The ledger is not an accounting system. It is a forcing function that stops a two-hour fix and a two-quarter migration from competing for the same sentence.

Item Interest rate Blast radius Reversibility Owner Pay-down rule
Missing index on order lookup High, on one query One service Two-way door Team that owns orders Fix in the next sprint that touches the query
Pricing rules duplicated in three services Medium, rising with each promotion Three teams Two-way door Domain owner for pricing Move behind one service when the next pricing change lands
One database shared by eleven teams High, on every schema change Whole platform One-way door until data is split Platform group with an executive sponsor Fund a migration with a named owner, an end date, and deletion of the old path
Synchronous chain across five services High during peak, hidden at low traffic Five teams, one user journey Partly reversible with an async path Architecture owner Add the async path, then retire the direct calls
Framework version three majors behind Low until a security advisory, then extreme Every service Two-way door if tests exist Each service owner, coordinated by platform Upgrade while the advisory is hypothetical, not after
  • Interest rate is what the item costs per change, multiplied by how often you make that change.
  • Blast radius is how many teams or user journeys feel it.
  • Reversibility is the door test above.
  • Owner is a named person or a funded group, never the team in the abstract.
  • Pay-down rule is the trigger that turns the item from a wish into scheduled work.

Fill the table with ten items, not a hundred. The point is to argue about the top three in a room with the people who own them, not to build a register that nobody reads. I would keep the table in the same place as the roadmap, because a pay-down rule that is not on the roadmap is a wish with better formatting.

Two more rules keep the ledger honest. Do not let an item enter without an owner, because an unowned item always scores as someone else's problem and sinks to the bottom. And do not let an item leave without a deletion, because we migrated and we are done are different claims. The old path still running is the most common way a paid debt quietly reappears.

How do you score each column without pretending it is precise?

Use a coarse one-to-five scale and force the comparison, not the decimal. Precision here is fake. Fowler's warning is blunt: the industry has no way of reasonably measuring productivity, and he puts the accuracy of cleanup estimates at pretty low (Cannot Measure Productivity, Technical Debt). A ledger with two decimal places is a spreadsheet pretending to be evidence.

So score by comparison, one column at a time.

  • Interest rate: 1 if the item costs minutes per change, 5 if it costs days.
  • Blast radius: 1 for one team, 5 for the whole platform.
  • Reversibility: 1 for a two-way door, 5 for a door that closes.
  • Owner: 1 if a named person exists and has capacity, 5 if ownership is contested.
  • Pay-down rule: 1 if a trigger and an end date exist, 5 if the item has been planned for three quarters.

Then apply two tie-breaks. Blast radius beats interest rate, because a small tax on everyone is worse than a large tax on one team. Irreversibility beats both, because a reversible item can wait and an irreversible one cannot. Everything else is a scheduling conversation you can have in fifteen minutes.

This is not an argument against measurement. The SEI has published an architecture-focused approach to measuring architectural technical debt precisely because debt that nobody quantifies loses every budget fight to a feature.

The point is to measure the thing that compounds, which is the cost of the next change, not the number of lines someone would like to rewrite. A metric that only counts remediation effort tells you how expensive a cleanup is. It tells you nothing about what carrying the debt costs you while you wait.

What is the pay-down rule that stops architectural debt compounding?

Pay interest with every change, and fund principal as a migration with an owner, an end date, and a deletion. Most teams do the first half and call it a strategy. The second half is the part that actually reduces the balance.

The rule has five parts, and you can apply all of them next quarter.

  1. Interest-pays-principal. When a change takes you through a debt item, leave the seam better than you found it. Fowler's version is gradual repayment: spend a little extra on the feature you are already doing, and the areas you touch most get cleaned first, which is exactly where the interest is highest (Fowler on gradual repayment).
  2. Never fund structural repayment from goodwill. Google's migration chapter is explicit that organic migrations tend to fail and that the cost has to be internalised somewhere (Google's large-scale change process). If an architectural item has no owner with allocated time, it is not planned. It is hoped for.
  3. One owner, one end date, one definition of done. Done means the old path is deleted and something prevents it from coming back. A migration that leaves the old system running forever has moved the debt, not paid it.
  4. Cap concurrent repayments. Two structural migrations at once is usually the most a platform group can absorb without stalling delivery. Three is how a pay-down programme becomes the reason features slip.
  5. Ratchet what you fixed. If the fix is not enforced, the next team reintroduces it, and you pay again. Enforcement is the whole subject of the module-boundary post, so I will only say here that a boundary without a failing build is a suggestion.

Paying interest is not the same as paying principal

There is a real difference between reducing the interest rate and clearing the balance, and teams blur it constantly. Renaming, extracting a function, adding a comment, and writing a better README are interest payments. They make the next change cheaper. They do not change the structure.

Splitting a shared database, moving a domain behind a service, or replacing a synchronous chain with an event is principal. It costs a quarter, it needs a migration plan, and it changes what future changes are possible.

Fowler's warning about the design payoff line is the honest version of this trade: teams that take on debt to go faster usually hit the point where design would have paid for itself in weeks rather than months (the design payoff line). Interest payments buy you time. Only principal changes the rate.

Why treating debt as a moral failing kills the ledger

Moralising debt turns a design decision into a character judgement, and the ledger goes quiet. If taking debt is shameful, nobody records it, and an unrecorded debt is one you cannot price.

Fowler's quadrant is the antidote. Split debt by deliberate versus inadvertent, and by prudent versus reckless (Technical Debt Quadrant). A deliberate, prudent debt is a release decision with a known payoff. A reckless one is a team that does not know the cost.

And the fourth cell, prudent but inadvertent, is unavoidable: even excellent teams discover what the design should have been only after a year of building.

That fourth cell matters most for architecture, because it is where most structural debt comes from. Nobody chose a shared database out of laziness. They chose it when the company had four engineers and one product, and it was correct then.

A ledger that starts with who did this produces defensiveness. A ledger that starts with what did it buy, and what does it cost now produces a decision.

I think the test is whether the person who wrote the code would recognise the constraint you are describing. If they would, you are discussing a trade. If they would not, you are discussing a decision that was never made consciously, which is a different conversation and usually a more useful one.

Why rewrite it is the wrong default for repayment

A rewrite is one repayment option, and usually the most expensive one. Treating it as the default is how teams trade a known system with known bugs for an unknown system with unknown bugs and a two-year delivery gap.

Joel Spolsky's account of Netscape is still the clearest warning: the rewrite was the single worst strategic mistake that any software company can make, because you throw away years of collected bug fixes, and because it's harder to read code than to write it (Things You Should Never Do, Part I). The old code looks chaotic partly because you cannot see why each wart exists. Each one is a bug fix for a customer you have not met.

There is a time when a rewrite is right, and Fowler names it. At roughly ten times its design load, the architecture that got you there is often the wrong one for the next stage, and a sacrificial architecture is a deliberate, honest choice rather than a failure. His examples are real: eBay's Perl scripts, then C++, then Java. The rule that keeps it sane is to sacrifice modules rather than whole systems, which you can only do if the boundaries are real.

Rewrite is a last resort, not a strategy

The middle path is well documented and badly underused. A strangler fig puts a facade in front of the old system and moves one capability at a time. Microsoft's architecture guidance describes the same pattern as replacing functionality incrementally while the legacy system keeps serving traffic (Strangler Fig pattern), and AWS's modernisation guidance applies it specifically to decomposing monoliths (strangler fig for monoliths).

The mechanics that make it survivable are catalogued as patterns of legacy displacement: transitional architecture, event interception, legacy mimic, parallel run. Parallel run is the one I would reach for first when the domain is money, because it lets both systems process the same input and compare answers before you trust the new one. Dark launch covers the other case, where you send a copy of live traffic to the new path and throw the response away.

Shopify's tax engine is the small proof: after years of isolating dependencies, swapping an inadequate legacy tax engine for a new calculation system became possible. Before that work, the post says, it would have been an almost impossible task.

Why dashboards that count TODOs measure nothing

A TODO count measures typing, not drag. Debt dashboards usually count comments, lint violations, or a debt ratio expressed in remediation minutes. All three measure how much work a cleanup would take. None of them measure what the debt costs you, which is the only number that belongs on the ledger.

This is Goodhart's law with a progress bar. DORA's own guidance warns that setting a metric as a goal increases the likelihood that teams game it (DORA's software delivery performance metrics). A team told to reduce TODO count will delete the TODOs. A team told to reduce a debt ratio will suppress the rules that produce it.

The honest instrument is the one you already have: delivery data, read per service and per area. DORA tracks change lead time, deployment frequency, failed deployment recovery time, change fail rate, and deployment rework rate. Rework rate is the one that moves first when architectural debt is biting, because work that was done keeps coming back. Pair that with the number of teams required to ship one change, which you can count by hand in an afternoon.

I would also count two things no dashboard will give you. How long a new engineer needs before their first production change, and how many meetings a routine schema change requires. Both are direct readings of structural coupling, both are cheap to collect, and both are hard to game because nobody enjoys reporting them as improving.

The survey numbers are worth knowing but not worth over-reading. Stripe's 2018 report found developers spending 17.3 hours a week on maintenance, debugging, refactoring, and modifying code, and roughly four more hours a week on bad code, and estimated roughly $85 billion a year in lost opportunity cost (The Developer Coefficient).

That is self-reported survey data across six countries, not a measurement of your system. Use it to justify looking. Do not use it as your number.

Does architectural debt show up in delivery metrics?

Yes, in lead time and rework, and almost never in output volume. This is the part that surprises managers who expect a visible slowdown. Teams rarely deliver less because of architectural debt. They deliver the same amount, later, with more coordination, and with more of it coming back.

Three signals move together when structural debt is the cause. Change lead time rises for one area while staying flat elsewhere. Rework rate rises, because changes that crossed a boundary were incomplete. And the number of people required for a routine change rises, which shows up as review latency long before it shows up in a dashboard.

What does not move is instructive. Lines of code, story points, and deployment counts can all look healthy while every change takes three weeks to coordinate. That is why DORA warns about making disparate comparisons between applications with different contexts: blended numbers hide the area that is actually constrained.

Look for the tell instead. If a change is blocked not by coding time but by waiting for another team's schema, another team's release window, or another team's approval, you are looking at architectural debt, and no amount of individual productivity will move it. That waiting time is invisible in a burndown chart and obvious in a room where three teams are asked who owns the table.

What this post is not — and what to read instead

This post is not a boundary-design tutorial: that is modular monolith vs microservices, which owns enforcement and the trade-offs of splitting.

It is not a rewrite playbook: how to evolve software architecture without rewriting covers the migration mechanics in depth.

And it is not an argument for patience as a virtue in itself. Slow productivity in software engineering explains why the sustainable pace and the pay-down rule are the same argument, not competing ones.

Related Reading