Tech debt as a balance sheet, not a feeling
Debt is the gap between the system you have and the one your roadmap needs. Price it against specific upcoming work and pay it inside that work, never in a scheduled debt sprint.
Tech debt is not bad code.
Plenty of ugly code is perfectly fine forever, because nothing will ever need to change it.
Debt is the gap between the system you have and the system your roadmap needs.
Which means it's only debt relative to what you're about to do, and the same code can be debt at one company and irrelevant at another with different plans.
That definition is worth adopting because it makes the whole thing arguable.
"This code is bad" is a matter of taste, and an engineer loses that argument to a deadline every time.
"The next three things on the roadmap each cost twice what they should because of this" is a business claim with a number attached to it.
Deliberate and accidental
Only one of these is worth feeling bad about.
Deliberate debt is a decision.
We know this isn't how we'd build it. We're doing it anyway, because we need to learn whether anybody wants this, and here's what we'd fix if it works.
Not a failure. It's how you go fast responsibly, and it's the right call rather more often than most engineers are comfortable with.
Accidental debt is what you get when nobody knew, or nobody was watching.
The design that made sense before the requirements changed. The pattern copied eleven times because it was the first example anybody found.
The reason to distinguish them: deliberate debt can be recorded.
When you take it on, write down what you did, why, and what would trigger fixing it. Now it's a known liability with a name on it.
Accidental debt only gets discovered when something takes four times longer than expected, which is the most expensive possible moment to find out.
The recording is worth doing even if you never look at the list again.
Half the value is that it forces a conscious decision at the moment you take the debt on, which is the only moment when the trade-off is still visible.
Attach the debt to the feature it will slow down
This is the mechanism that makes the work actually happen.
An abstract debt backlog never gets prioritised against features.
It's engineering asking for time to make engineering nicer. That framing loses, and it deserves to, because it doesn't answer the question of what the company gets.
So don't have a debt backlog.
Attach each piece of debt to the specific upcoming work it taxes, and price it there.
"The notifications rework is on the roadmap for next quarter. Three weeks as things stand, one week if we fix the event dispatch first, and that's four days. So four days now buys two weeks in eight weeks' time."
That's a conversation any sensible executive can have with you, and it usually goes well, because you've turned a values argument into arithmetic.
The corollary is uncomfortable and correct.
Debt that isn't slowing down anything on the roadmap doesn't get fixed. It stays ugly. That's genuinely fine, and engineers hate it.
But every hour spent improving code nothing will touch is an hour taken from something that matters, and the psychological cost of walking past it is smaller than the cost of the habit that fixes everything.
Inside the work, not in a sprint
I don't run debt sprints, cleanup weeks, or a fixed percentage of capacity allocated to refactoring.
All three fail the same way.
A debt sprint separates the fixing from the context.
An engineer improving a system in isolation has no real requirement telling them what "better" means, so they build for imagined future needs and produce a more general system that's harder to change in the direction you actually end up going.
A fixed percentage becomes a budget.
Budgets get spent on whatever is nearest rather than whatever matters, and they get cut first when the quarter goes badly. It also says, implicitly, that the other eighty percent of the time you're allowed to make things worse.
What works is doing it inside the feature work.
You're building notifications, the event dispatch is in the way, you fix it as part of the feature. The estimate includes it.
Nobody asks permission. Nobody funds a separate initiative.
Two things have to be true for that, and both are cultural rather than procedural.
Engineers need the standing to say "this piece will take a week and a half, three days of which is fixing the thing underneath it," and to be believed when they say it.
And nobody should be asked to justify the difference line by line. That's a fast way to teach people to hide it in the estimate instead, which they will, and then you've lost the visibility as well as the trust.
The rewrite conversation
Every few years somebody proposes replacing a system entirely.
Usually wrong. Occasionally very right.
The wrong version, in its most common form. The system is hard to understand, the current team didn't write it, and a rewrite promises a clean slate.
What that misses is that the existing system encodes years of accumulated correctness. All the edge cases. The weird customer. The workaround for the vendor's bug in 2023.
A rewrite discovers those one production incident at a time, and takes two to three times the estimate, which held so consistently for so long that it was reasonable to treat it as a law.
The law was about cost, and the cost moved
That rule held because rewriting meant human-years. When the writing is machine time, that bill largely goes away, and the other one, rediscovering everything the old system quietly knew, doesn't move at all. So the constraint relocates rather than disappearing. A rewrite is cheap to build now and expensive to specify, which means the ones that fail from here fail for the reason they always failed, except that it's now the only reason left. The full argument has its own piece, including why cheaper rewrites make bad rewrites more likely rather than less.
What belongs here is the reconciliation, because this sits oddly beside the argument against debt sprints above. The objection to a debt sprint was never that fixing things deliberately is wrong. It was that separating the fixing from the context produces work aimed at imagined requirements, and that scale and duration are what make that separation dangerous. A two-day rewrite of a bounded component has neither problem. A six-month one still has both.
The right version has a specific form. The core assumption of the system is wrong, and no amount of incremental change fixes it.
Single-tenant when you need multi-tenant. Synchronous when the domain is fundamentally asynchronous. Built on something that no longer exists.
Test it with a question.
Can we get from here to there in a sequence of steps, each of which ships and each of which leaves the system working.
Yes means do that, always, even when it's slower on paper. Strangler-fig migrations are less satisfying and vastly more likely to finish.
If the honest answer is no, and sometimes it is, treat it as a real project with a real plan.
What ships first. How the two systems coexist. What the rollback is, and the explicit decision point at which you'd stop.
And expect to be running both for longer than anybody wants. That's where most of the actual cost lives.
Measuring it without a spreadsheet
You don't need a debt metric.
You need to notice when things are getting slower in a particular area.
The signals I'd actually watch.
Estimates for work in a given part of the system creeping up over time. The same area turning up repeatedly in incidents.
And the one that's most reliable and least measurable: engineers visibly not wanting to touch something.
That last one is worth taking seriously rather than dismissing as preference.
Reluctance to change a system is usually accurate information about its condition, held by the people with the best view of it. And it shows up months before the estimates do.