Engineering
The real cost of technical debt
Technical debt is not messy code. It is the gap between what your system assumes and what your business now needs — and it is priced in decision latency.
"Technical debt" has become a synonym for code somebody dislikes. That reading makes it impossible to prioritise, because taste is not a business case.
A more useful definition: technical debt is the distance between the assumptions your system was built on and the reality it now operates in. Code can be ugly and carry no debt — if nothing about its context has changed, it is simply working. Code can be pristine and carry enormous debt, if it was designed around a single-tenant model and you now sell to enterprises who require isolation.
The distinction matters because it tells you which debt to pay down: the debt sitting between you and something you actually need to do.
What it actually costs
The cost is not "slower development" in the abstract. It shows up in specific, measurable places.
Decision latency. The clearest symptom of expensive debt is how long it takes to answer "can we do X?" In a healthy system that is a short conversation. In an indebted one it becomes an investigation, because nobody is confident what a change touches. That delay compounds across every roadmap discussion.
Estimate variance, not estimate size. Debt does not make estimates bigger so much as it makes them unreliable. A team that says "two weeks" and lands within a few days is workable. A team that says two weeks and takes six, unpredictably, cannot be planned around — and that unpredictability is what erodes trust between engineering and everyone else.
Onboarding time. How long before a new engineer ships to production unsupervised? When that stretches past a month, essential knowledge usually lives in people rather than in code. That is debt with a retention risk attached.
Change amplification. One logical change requiring edits in six unrelated places is the most honest signal of all. It means the structure does not match the domain, and it predicts where the next bug will be.
The debts worth paying
Not all of it should be repaid. Debt in code that never changes costs you nothing — refactoring a stable module is an expense with no return.
Prioritise where these overlap:
- The change is slow — measured, not felt.
- The area changes often — check the commit history rather than trusting impressions.
- Something on the roadmap requires it — there is a real thing this unblocks.
Where all three are true, the work has a business case that does not require anyone to care about elegance.
How to fund it
The two common approaches both tend to fail.
A dedicated refactoring sprint gets cut the moment a deadline appears, because it is the only item with no customer-visible outcome. A fixed percentage of every sprint sounds disciplined but decays into a slot people fill with whatever is bothering them that week — which is taste-driven again.
What works better is attaching it to delivery. When a feature requires touching an indebted area, the cleanup goes into that feature's scope and estimate. It becomes part of doing the work properly rather than a competing priority, and the debt paid is by definition debt that was in the way.
Where it comes from
Some debt is a deliberate trade — ship now, generalise later — and that is legitimate engineering, provided it is recorded. Most debt is not a decision at all. It accumulates because a choice was made for a context that quietly changed, and nobody wrote down what the choice assumed.
Which is the argument for architecture decision records, and the reason we insist on them: not documentation for its own sake, but so that when the context shifts, the affected decisions can be found. Debt you can locate is debt you can price. Debt nobody can explain is the expensive kind.