Technical Debt Is Not the Enemy. Unpriced Technical Debt Is.
Debt is a tool when you know its cost. How to measure technical debt through change cost, risk and toil, decide what to pay down, and talk about it in terms the business funds.
- 11 minit bacaan
Halaman ini belum tersedia dalam Bahasa Melayu, jadi anda sedang membaca versi asal bahasa Inggeris.
Ward Cunningham, who coined the term in 1992, compared shipping not-quite-right code to taking on debt: it speeds you up now, and you pay interest every time you work around it later. That framing is useful precisely because debt is not bad in itself. Startups, launches and experiments run on it. What hurts teams is debt that nobody has priced: it grows silently, shows up as “everything takes longer”, and competes with features without numbers to argue for it. This post shows how to price technical debt and turn it into decisions.
Why It Matters
Unpriced debt fails in two opposite ways:
- Ignored: the team slows down until a rewrite looks like the only way out.
- Treated as all bad: the team spends months polishing code that never changes.
Pricing debt, in time, risk and money, lets you pay down the debt that is actually costing you, and argue for it in terms product and business leaders can weigh against features.
Two Real Cases of Unpriced Debt
Not All Debt Is the Same
Martin Fowler’s technical debt quadrant separates how the debt was taken on:
| Reckless | Prudent | |
|---|---|---|
| Deliberate | “We don’t have time for design.” | “We must ship now and deal with the consequences.” |
| Inadvertent | “What’s layering?” | “Now we know how we should have done it.” |
- Prudent and deliberate is a business decision: ship the launch, write down what you skipped and when you plan to fix it.
- Prudent and inadvertent is unavoidable: you learn the right design by building the wrong one first.
- Reckless debt, deliberate or not, is a skills or process problem. Fix it with review, pairing and standards, not with a debt budget.
The prudent kinds are healthy if recorded and priced.
Where Debt Hides
Debt isn’t only messy code. The expensive kinds are often elsewhere:
| Kind | What it looks like | The signal that it’s costing you |
|---|---|---|
| Code | Duplicated logic, tangled modules, unclear names | Every change in the area takes longer than elsewhere |
| Architecture | Shared databases, services that must deploy together | Features need coordinated releases across teams |
| Dependencies | Old framework versions, unmaintained libraries | Security patches you can’t apply; upgrades nobody dares to start |
| Tests | Missing, slow or flaky tests | People rerun CI “to see if it passes”, or avoid refactoring |
| Infrastructure and operations | Manual deploys, snowflake servers, missing alerts | Toil hours every week; incidents found by customers |
| Knowledge | One person who understands a system; no docs | Work waits for that person; onboarding takes months |
| Data | Inconsistent schemas, unclear ownership, bad historical data | Reports disagree; migrations stall |
Interest Is Paid Where Code Changes
Debt only costs you when you touch it. A messy module that nobody has changed in two years has almost no interest. A tangled module that every feature passes through charges interest on every change.
Your version control already knows where change happens. Counting commits per file over the last few months (often called “churn”) and overlaying it with complexity, ownership spread or incident links gives a rough heat map in an afternoon. Adam Tornhill’s work on behavioral code analysis builds on exactly this idea.
Interest also compounds. Code that is hard to change attracts workarounds, each workaround makes the next change harder, and the cost per change keeps rising until a team spends most of its time working around its own code:
Pricing the Debt
Price each significant debt item by the interest it charges today:
| Interest | How to measure it |
|---|---|
| Slower changes | Lead time or cycle time for changes touching the area, compared with the rest of the codebase |
| Defects and incidents | Bugs and incidents linked to the area, and their customer impact |
| Toil | Hours per month spent on manual steps, workarounds and repeated fixes |
| Risk | Probability × impact of a failure: dead code that can still run, an unpatched dependency, a system near its capacity limit, a single person who understands it |
| Opportunity cost | Features postponed or made impossible because of the area |
Then estimate the principal: what it would cost to fix. A debt item with high interest and a modest principal is an easy decision. Writing the estimate as a sentence product leaders can weigh helps more than a backlog label. For example:
“Checkout changes take 2× longer than the rest of the codebase and caused 3 of last quarter’s 7 incidents. Splitting the pricing module costs about 3 engineer-weeks and should pay back within one quarter.”
The numbers are estimates, and that is fine: an approximate price is far better than none, and you can check it after the work.
Paying It Down
- Record debt when you take it on: what, why and the trigger to fix it
- Price the items in frequently changed code
- Budget a steady share of every cycle, not one-off debt sprints
- Pay down alongside features that touch the same code
- Check the price afterwards: did lead time or incidents improve?
- Fix it where you work. The cheapest moment to improve code is when a feature already requires changing it. Leave the area better than you found it, in small reviewed steps.
- Prefer incremental replacement over rewrites. Rewrites stop delivery, take longer than planned and lose behavior nobody wrote down. Replace systems piece by piece behind a stable interface (the “strangler fig” approach), so you can stop at any point with something working.
- Make new debt visible. A
TODOwithout an owner and a trigger is a wish. Link it to a ticket, or record significant decisions as architecture decision records with their consequences. - Watch the trend, not the backlog size. A growing list of debt tickets says little; rising lead time and incident counts in the same areas say a lot (see the metrics that make engineering organizations worse for how to use such numbers without gaming them).
Going Deeper: Making Debt Decisions Like an Investment
Keep a Debt Register
A backlog label (“tech-debt”) hides the information needed to decide. A short register, one row per significant item, makes debt comparable with feature work:
| Field | Example |
|---|---|
| What | Pricing logic duplicated in checkout and the admin tool |
| Why it exists | Shipped the admin tool quickly for the March launch (deliberate, prudent) |
| Interest today | About 2 extra days per pricing change; 2 incidents last quarter from the copies drifting apart |
| Risk | Medium: the next tax rule change must be made twice |
| Principal | About 6 engineer-days to extract one pricing module |
| Trigger | Before the tax change planned for Q3, or at the next pricing incident |
| Owner | Checkout team |
Prioritize by Cost of Delay
When several items compete, ask what each one costs per week of waiting, not just how big it is. Cost of delay divided by the effort to fix it gives a simple ranking (this is the idea behind “weighted shortest job first”): a modest fix to a module that slows every release beats a large rewrite of something that rarely changes. Items with a hard deadline, such as an end-of-life runtime, a certificate or a regulation, jump the queue when the deadline nears.
An illustration with three items (estimates, in engineer-days):
| Debt item | Cost of delay per week | Effort to fix | Ratio | Order |
|---|---|---|---|---|
| Flaky checkout tests (reruns, blocked releases) | 3 days | 4 days | 0.75 | 1st |
| Duplicated pricing logic (every pricing change done twice) | 2 days | 6 days | 0.33 | 2nd |
| Rewrite of the old admin tool (works, rarely changes) | 0.5 days | 40 days | 0.01 | Last, or never |
The flaky tests win despite being the least interesting work: they cost the most per week of waiting, for the least effort.
Talk About It in the Business’s Terms
Leaders fund what they can weigh. Present debt as a trade-off they already understand: “this costs us roughly two engineer-weeks per quarter and slows every checkout change; fixing it takes three weeks and pays back within two quarters,” or “this is the same kind of risk that cost Knight Capital its business: dead code that can still run.” Then report afterwards whether lead time or incidents actually improved. Teams that close the loop get their next debt budget approved more easily.
Know When Not to Pay
Some debt is best left alone: code that is about to be deleted, prototypes that will be thrown away, and stable code nobody changes. Paying debt there is spending without a return.
Test Yourself: Scenarios
Think through your answer, then compare.
Senario 1
“Can We Have a Refactoring Sprint?”
Your team asks for a two-week sprint to “clean up technical debt”. Product says no: the roadmap is full. How do you get the important debt fixed?
Lihat jawapanSembunyikan jawapan
Stop asking for an undefined sprint.
- Pick the two or three items with the highest interest in frequently changed code.
- Price each: what it costs today (slower changes, incidents, toil) and what fixing it costs.
- Propose paying them down alongside the features that touch the same code, plus a steady share of capacity (say 15%) instead of a one-off sprint.
- Agree on the proof, such as lead time for changes in that area, and report it.
Concrete, priced items with a measured payoff are much easier to approve than “cleanup”.
Senario 2
The Legacy Module Nobody Touches
A 10-year-old module has no tests, inconsistent style and outdated patterns. It changes about once a year and hasn’t caused an incident in that time. A new engineer wants to rewrite it.
Lihat jawapanSembunyikan jawapan
It costs almost nothing today: interest is paid only where code changes. A rewrite would spend weeks and risk losing behaviour nobody documented.
Leave it, but reduce the risk:
- add a few characterization tests that pin down its current behaviour
- make sure more than one person understands it
- isolate it behind a clear interface, so it doesn’t spread
Revisit if it starts changing more often or a dependency it uses reaches end of life.
Senario 3
Shipping a Deliberate Shortcut
To meet a launch date, the team decides to hard-code three partner configurations instead of building a configuration system.
Lihat jawapanSembunyikan jawapan
That can be a good decision: prudent, deliberate debt. Make it explicit:
- Record what was skipped, why, and the trigger to revisit (for example “before the fourth partner” or “by end of Q2”), in a ticket or an architecture decision record.
- Contain the shortcut in one module behind an interface, so the real system can replace it without touching callers.
- Tell product that the fourth partner will cost extra until it is paid down.
Debt that is recorded, contained and priced is a tool; debt that is forgotten becomes a surprise.
Senario 4
An End-of-Life Framework
The framework your main app uses reaches end of life in nine months: no more security patches after that.
Lihat jawapanSembunyikan jawapan
This is debt with a deadline: its risk becomes unacceptable on a known date.
- Estimate the migration now, including the riskiest parts.
- Plan incremental steps (upgrade path, codemods, module by module behind the same interface), with milestones well before the deadline, so you aren’t forced into a rushed big-bang change.
- Make the risk visible to leadership with the date and the consequence (“unpatched vulnerabilities in the customer-facing app”).
- Budget for it explicitly in the roadmap.
Key Takeaways
- Technical debt trades future effort for speed now; it is a tool when it is recorded and priced.
- You only pay interest where code changes, so prioritize debt in frequently changed, hard-to-change areas.
- Price debt by its current interest (slower changes, incidents, toil, risk) and its principal (the cost to fix). Risk is the column teams forget: Knight Capital lost $460 million to dead code nobody removed.
- Pay it down continuously with a steady budget, alongside related features, and replace systems incrementally.
- Check afterwards whether lead time and incidents actually improved.
Sources
- Martin Fowler: Technical debt
- Black Swan Farming: Cost of delay
- US Securities and Exchange Commission: Order instituting proceedings against Knight Capital Americas LLC (2013) and press release
- US Department of Transportation: DOT penalizes Southwest Airlines $140 million for 2022 holiday meltdown (2023)
- Reuters, via Daily Maverick: US fines Southwest Airlines $140 million after 2022 meltdown
- Ward Cunningham: The WyCash portfolio management system (OOPSLA 1992)
- Martin Fowler: Technical debt quadrant
- Martin Fowler: Strangler fig application
- Adam Tornhill: Your Code as a Crime Scene, 2nd edition (Pragmatic Bookshelf, 2024)
- DORA: Software delivery performance metrics
Tag
Soalan lazim
How much time should a team spend on technical debt?
There is no universal number. Many teams reserve a steady share of each cycle, often 10–20%, and adjust it based on evidence such as rising lead time or incident counts. A steady budget works better than occasional “debt sprints”.
Is all old code technical debt?
NoOld code that works, rarely changes and causes no incidents costs nothing to keep. Debt matters where it slows down changes you actually need to make, or creates risk.
When is a rewrite justified?
Rarely, and only when incremental change is genuinely impossible. Prefer replacing a system piece by piece behind a stable interface, so you keep shipping and can stop at any point.
What is the difference between technical debt and bad code?
Technical debt is a trade-off: a known shortcut taken for speed, with a cost you can name. Bad code that nobody chose is still a cost, but calling everything debt hides the difference between deliberate decisions and quality problems.
How do I measure technical debt?
Measure its interest rather than the code itself: lead time for changes in the area, incidents and defects linked to it, hours of toil and workarounds, and risks such as unsupported dependencies. Static analysis scores alone don’t show what the debt costs.
What is a debt register?
A short list of significant debt items with what each is, why it exists, the interest it costs, the cost to fix it, a trigger to revisit it and an owner. It makes debt comparable with feature work.
How do I convince product managers to prioritize debt?
Bring specific items with their cost in terms they track, such as slower delivery of their features, incidents and risk, a proposed fix with an effort estimate, and a way to show the result afterwards. Pair debt work with the features that touch the same code.
What is cost of delay?
How much value is lost for each week a piece of work waits. Ranking debt items by cost of delay divided by the effort to fix them puts the most expensive problems first.