ISSUE 2026-09-27SERIES BREC 45TYPE BRIEFgarnetgrid.com
The question is not how much
Insights
Nobody can tell you what technical debt costs your company per year, and the people who offer a figure are usually selling something. But you can find out which compromises are taxing the work you have already committed to, using measurements that take an afternoon and come from your own repository rather than someone else's survey.
Every conversation about technical debt above the level of a code review arrives at the same request: put a number on it. How much is it costing us a year? There is no honest answer to that, and a confident one should make you suspicious. The answerable question is narrower: which of the compromises in this system is taxing the work we have actually committed to over the next two quarters, and how would we know if we were wrong? That sounds like a dodge. It is the opposite. It converts an impossible accounting exercise into a handful of things you can go and measure in your own codebase today.
The metaphor flatters the problem
Ward Cunningham's borrowing metaphor was a device for explaining to non-programmers why shipping before you fully understand a domain can be rational. It has since been promoted into a theory of cost, which it cannot carry. Debt has a principal you chose to borrow, a rate, and a schedule. Most of what teams label technical debt has none of those. Nobody decided anything; the system drifted, one locally reasonable choice at a time.
Martin Fowler's quadrant is the refinement that helps: compromises can be deliberate or inadvertent, and prudent or reckless, and those four things behave nothing alike. Deliberate, prudent debt really does resemble a loan and can be managed like one. Inadvertent debt is not debt, it is ignorance you have not yet priced.
If you want a closer instrument than a loan, it is a written option. You took delivery early in exchange for promising flexibility later. The cost lands when someone exercises that option, and you do not choose when. In practice it is the quarter you committed to something ambitious.
The cost arrives as variance, not a slower average
A team carrying heavy debt is not uniformly slow, which is why the problem is so hard to argue about. Most changes still land in a day, the throughput graph looks unremarkable, and whoever says the codebase is a liability sounds like they are angling for a refactor holiday.
What actually happens is divergence. Two changes of the same nominal size go different ways depending on where they land. One touches a module with clean seams and ships by lunch. The other needs a schema change, which needs a backfill, which needs a migration window, which needs the service nobody has restarted since March. Three weeks.
Variance, not the mean, is what wrecks an organisation's relationship with its engineers. You can plan around a team that is consistently slow. You cannot plan around a long right tail, because every commitment becomes a bet, and after a few lost bets the planning process stops believing any estimate at all.
If someone insists on a figure, here is one that is both honest and local. Take changes of roughly similar nominal size from your own tracker and look at the spread of elapsed time rather than the median. The gap between your median and your slowest tenth is your debt bill, denominated in the currency that actually hurts, which is predictability. It will not generalise to anyone else's company. That is a feature, not a limitation.
The expensive debt sits in the path of every change
Debt does not compound evenly. Some of it lies in the path of everything you will ever do, and some of it sits in a corner being ugly.
In the path: the loop from editing a file to observing real behaviour, the data model, the authentication and tenancy boundary, whatever plane configuration actually resolves through, and the boundaries of your deployable units. A compromise here is paid by every engineer on every task, indefinitely.
In the corner: an over-long file, duplication between two components nobody edits, a bad name. Usually these are aesthetic complaints wearing a finance costume. Duplication in particular is often cheaper than the abstraction someone wants to replace it with, and a premature abstraction is debt that looks like an asset on the way in and is far harder to reverse than a copied function.
The loop deserves singling out. If it takes forty minutes to get from saving a file to seeing whether the change did what you intended, debugging stops being a sequence of experiments and becomes a sequence of guesses, because you can only afford a few a day. Engineers adapt by making larger, more speculative changes, which fail in more entangled ways, which lengthens the next loop. That is real compounding, and shortening the loop is usually the highest-return payment available. It is also the least likely to be funded, because it never appears on a roadmap.
The worst of it is behaviour that lives outside the repository
The debt that does real damage is not ugly code. Ugly code is visible, and visible problems eventually get fixed. The dangerous kind is behaviour the repository does not describe.
The signs are specific. A port, a route, a feature flag or a price written in more than one place, with no declared owner and no generated mirror. Rules applied at the edge or in a proxy that rewrite what the application returns. Configuration that exists only on the running machine, because somebody fixed an incident by hand and the fix worked. Override files that the deploy process knows nothing about.
When this is bad enough you can read every line of the codebase and still not know what the system does. That is not an inconvenience, it is the end of reasoning. Every change becomes a gamble, because the artefact you edited is not the artefact serving traffic and the difference between them is written down nowhere.
There is a cheap test. Pick one fact about your system that has a single correct value and count how many places it is written. If the answer is more than one and none of the copies is generated from the others, you have found the shape of your next incident. Do that for five facts and you will have an inventory no static analysis tool produces, because the copies sit across systems — code, edge rules, machine configuration — rather than inside one file.
Observability debt hides every other kind
This is the one I would fix first, and it is an opinion rather than a settled position.
A check that cannot fail is worse than no check, because it converts ignorance into confidence. The forms are recognisable. A dashboard that reads an empty table and reports everything healthy, because zero failures and no data are rendered the same colour. A suite that has genuinely proved a bug is absent but has never once proved the feature works. Alerting that has never fired, which from the outside is indistinguishable from alerting that cannot fire. A monitor pointed at a path the producer stopped writing to two refactors ago.
This sits above the rest because it invalidates measurement. Every estimate you make about your debt, including the comfortable ones, is downstream of instruments you have never tested. A negative reading needs a positive control: if you cannot make a check fail on purpose, you do not know what its silence means.
Until the instruments are trustworthy, a debt programme is theatre. You will pay down whatever irritates the loudest engineer, and afterwards you will have no way to tell whether it helped.
Paying it down without starting a rewrite
One rule does most of the work. Pay down the debt that lies in the path of work you have already committed to, and leave the rest alone, explicitly and in writing.
Debt in a region nobody will touch this year is costing you nothing this year. Saying so out loud matters, because unacknowledged debt works as a permanent guilt tax and guilt makes people prioritise badly. Write down what you are choosing not to fix, and why. That list is a decision, not an admission.
It follows that payments should ride along with the feature that needs them rather than sitting in a hardening quarter. Work bundled with a delivery has a sponsor, a definition of done, and a real test: the feature either got easier or it did not.
On rewrites, the failure mode is worth naming precisely. It is rarely scope. It is that the only complete specification of the old system is the old system, including the production configuration nobody wrote down, the edge rules, and the hand-applied fix from an incident three years ago. A rewrite is a promise to reproduce behaviour you cannot enumerate. If you are seriously considering one, the first deliverable is the enumeration, not code. If that turns out to be impossible, you have learnt the most valuable thing on offer, which is that incremental replacement is your only real option.
Things worth counting, because they are cheap to measure and hard to argue with:
Stop hunting for the industry figure. The number you want is the spread between your median change and your slow ones, and it is already sitting in your tracker. Then give the next month to three things: shorten the loop from edit to observed behaviour, reduce every fact that matters to one authoritative place, and prove your instruments can fail. None of that will look like a debt-reduction programme to a finance team, and all of it attacks the variance that is actually hurting you. Write down the debt you are deliberately keeping so it stops haunting planning, and then get back to shipping. The point was never a clean codebase. It was being able to promise something and mean it.