Making technical debt visible enough to pay
Debt nobody can see never gets budget. Give each item a cost, an interest rate and a place on the same list as features, then pay it inside feature work.
Every quarter someone on the team asks for two weeks to clean up a module, and every quarter the answer is that there is no room this time. Nothing about that answer is unreasonable. On one side of the table there is a feature with a customer and a date attached, and on the other there is a request supported by the sentence "it will be much better afterwards". Debt that nobody can see does not get budget, and the visibility problem is ours to fix.
Why invisible debt always loses
Work gets prioritised by comparison, and comparison needs the same units. A feature arrives with an estimate, a customer and a revenue story. A cleanup arrives with an adjective. Even a sympathetic manager cannot put those two things on one list and choose between them honestly.
The fix is to give every debt item the same three fields a feature has: what it costs, what it is worth, and what happens if we do nothing. The last one is where debt differs from a feature, because doing nothing has a running cost. That is the interest, and it is the number that changes the conversation.
Deliberate debt and mess are different conversations
Debt in the original sense is borrowed deliberately: the team knew the better design, chose the quicker one to meet a date, and understood that it would cost more later. That is a legitimate trade and sometimes the right call.
Mess is what appears when nobody chose anything. Two ways of doing the same thing because two people arrived at different times. A module nobody understands because the person who wrote it left. Dead code nobody dares delete.
They need different framing. Deliberate debt is a scheduling question: we borrowed, here is the balance, here is when we planned to pay it. Mess is a quality question, and calling it debt gives it a respectability it has not earned. Deliberate debt also has one advantage: it can be written down when it is taken, which is exactly what a one paragraph decision record is for. A record that says "chosen under release pressure, revisit when the second integration lands" turns a future argument into a scheduled task.
Give each item a cost and an interest rate
One short block per item, in the same tracker as everything else:
Item: the order import writes to three tables with no transaction
Hurts: any change to order fields needs all three paths tested by hand
Evidence: touched in 9 of the last 20 releases, 2 incidents in 6 months
Cost: about 3 days to wrap in a transaction and add two tests
Interest: roughly half a day of extra work per release, plus incident risk
Origin: deliberate, taken when the third table was added to meet a dateThe evidence line is the one that does the work, and it comes from data you already have. How many of the last twenty releases touched that module. How much of each estimate in that area was padding for the same fragility. How many incidents traced back to it. None of it needs to be exact. A rough number everybody can check beats a precise one nobody believes, and this is the same discipline as measuring before you optimise: a number that exists ends arguments that an adjective keeps alive.
Interest is the field people forget, and it is the only one that makes waiting expensive. An item with a real interest rate gets paid. An item with a fix cost and no interest looks like an optional purchase, and optional purchases get deferred forever.
Two consequences follow, and both save money. Code that nobody touches has no interest no matter how ugly it is, so it can stay ugly. Code that everything passes through can be modestly untidy and still be the most expensive thing you own.
Put it on the same list as features
A separate technical debt board is where debt items go to die quietly. Nothing on it ever wins against something on the main list, because the two lists are never compared in the same meeting.
Re-read the list when the roadmap changes rather than on a calendar. Interest is not constant: a module nobody has visited for months becomes expensive the moment three items in the next quarter land on top of it. Five minutes during planning, asking which debt items the new work passes through, usually picks the item to pay that quarter on its own. What makes an item payable is rarely how bad it is, it is that the road now goes through it.
One list, same units, same review. When a debt item and a feature are next to each other with costs and consequences written down, a reasonable person can pick, and sometimes they will pick the feature. That is a fine outcome. The goal is that the trade is made on purpose rather than by default, and that the interest keeps accruing in public.
Pay it inside feature work
The grand refactor is the most reliable way to waste a quarter. It spends months before it returns anything, it is compared against features the entire time, and when it gets paused halfway the codebase ends up carrying two designs at once, which is worse than either.
What works instead is unglamorous. When a feature lands in the same area as a debt item, the fix goes into that piece of work and the estimate says so out loud: eight days rather than five, with the reason written next to it. The cleanup ships with the feature, gets exercised by the same traffic, and is covered by the same tests. If the release has to be rolled back, one change goes back rather than two months of work, which is the whole argument for small reversible steps.
A rule of thumb that has survived several teams: no single change is allowed to be more than about a fifth cleanup, and no cleanup crosses a module boundary on its own. Larger than that and it stops being a pull request people can review, which is when the risk starts to stack up.
Say it to a client in their language
Clients do not buy refactoring, and there is no reason they should. They buy delivery dates, cost, and the absence of unpleasant surprises. Translate it once and the conversation changes:
Option A: add the new report on the current import.
5 days now. Every later change in this area carries the same
2 day surcharge, and the duplicate order problem stays possible.
Option B: add the report and make the import transactional first.
8 days now. Later changes go back to normal cost, and the
duplicate orders stop happening.Nobody has to say the word debt. The interest rate is visible as a surcharge on all future work, the fix has a price, and the decision belongs to the person whose money it is. In my experience a client who declines option B still remembers the surcharge, and brings it up themselves the third time it appears in an estimate.
Two things make this land. Use their numbers, taken from work you have already done together rather than from industry claims. And never present it as a warning about the last team, even when it would be fair, because the moment it sounds like blame it stops being a decision and becomes a defence.
What to watch out for
- Debt theatre. A backlog full of items with no interest rate is a list of complaints. If you cannot say what it costs to leave an item alone, it is not ready to be prioritised.
- Using debt as a route to a rewrite. A rewrite is a new project with the old requirements forgotten, and it should be argued on its own terms, with its own budget and risk.
- Tidying where nobody works. The satisfying cleanup is often in the file you find annoying rather than the file the team edits weekly. Follow the evidence line, not the irritation.
- Letting the interest estimate be exact. A fake precise number invites an argument about the number instead of the decision. Round it, show how you got it, and move on.
Debt becomes payable when it stops being a feeling and turns into a line item with a price and a running cost. That takes an afternoon of writing, not a new process. Pick the three places your team works around something every week, write the six lines for each, and put them on the same list as everything else. The ones that deserve to be paid start winning on their own, and the ones that never win turn out to have been cheaper to live with than to fix, which is also worth knowing.
Questions and answers
- How do I convince a client to pay for technical debt?
- Stop asking for refactoring time and start pricing the same feature two ways: once on the current code and once with the fix included. The first estimate carries a note that every later change in that area costs the same surcharge. Clients decide in time and money, and two numbers side by side is a decision they can actually make.
- What is the interest rate on technical debt?
- How much slower and riskier each release becomes while the item is unpaid. Measure it with what you already have: how many of the last twenty releases touched that module, how much of each estimate was padding for it, how many incidents came out of it. A rough number that everyone can check beats a precise one that nobody believes.
- What is the difference between deliberate debt and mess?
- Deliberate debt was a choice made with a reason, usually a deadline, and it can be written down and revisited. Mess is what accumulates when nobody chose anything: dead branches, three ways of doing the same thing, a module nobody understands. Deliberate debt is a scheduling conversation. Mess is a quality conversation, and calling it debt makes it sound more respectable than it is.
- Is a big refactor ever the right answer?
- Rarely, and almost never as a separate project with its own timeline. A long refactor spends months before it returns anything, it competes with features the whole time, and it gets cancelled halfway leaving two designs in the codebase. Attaching the work to the features that touch the same code delivers value each week and keeps the change reversible.