Tech debt as a portfolio: how to get it paid down
"We need a refactor sprint" rarely gets approved; "this costs us two days every release" does. How to classify debt, measure its interest, pick what to pay first, and bundle paydown with work the business already wants.
Every engineering team has tech debt, and almost every team has tried to get time to fix it with some version of: “We need a refactor sprint.” It rarely works. Not because leaders don’t care about code quality, but because the request doesn’t tell them what they’re buying.
The framing that works treats debt the way finance does: as a portfolio, where each item has a principal and an interest rate.
Principal and interest
- Principal is what it would cost to fix: engineer-weeks, risk, coordination.
- Interest is what it costs you every week you don’t: slower delivery, incidents, on-call load, onboarding time, blocked features.
Ugly code with no interest — a messy module nobody touches — isn’t urgent, however much it offends you. Clean-looking code with high interest — a configuration system that causes a production issue every month — is.
The picture at the end of this post is a joke, but the iceberg is real: the debt you can see is rarely the debt that costs you most, and a shortcut taken once keeps charging interest every time someone works around it.
Classify what you have
Not all debt is the same kind of problem:
| Kind | Example | Typical interest |
|---|---|---|
| Design debt | Two services share a database table | Every schema change needs two teams |
| Code debt | A 3,000-line class everyone fears to touch | Slow changes, regressions |
| Test debt | No tests around billing | Fear-driven releases, manual QA |
| Operational debt | Manual steps in deploys or migrations | Toil, human error, slow recovery |
| Dependency debt | Framework or runtime versions near end of support | Security exposure, a forced upgrade later at a worse time |
| Knowledge debt | One person understands the payment reconciliation job | Risk concentrated in one calendar |
| Unfinished migrations | Old and new paths both live behind flags | Double maintenance forever |
Measure the interest
You don’t need precision; you need evidence. Good sources:
- Lead time: how long changes to this area take compared with others.
- Incidents: how many recent incidents involved this area.
- Toil: hours per week spent on manual work it causes.
- Blocked work: features on the roadmap that are slower or impossible because of it.
- Developer surveys: “which part of the codebase slows you down most?” is a surprisingly accurate signal.
One concrete sentence beats a page of adjectives: “Every release needs 2 engineer-days of manual config changes across 14 services, and 3 of the last 10 incidents were config mistakes.”
Keep a register
A simple debt register — a table, not a tool — makes debt visible and comparable:
| Item | Kind | Interest (evidence) | Principal | Next feature that touches it | Owner |
|---|---|---|---|---|---|
Shared orders table |
Design | 2 teams per schema change; 1 incident/quarter | 6 weeks | Order history API | Payments team |
| Manual release config | Operational | 2 eng-days per release | 2 weeks | — | Platform |
| Billing tests | Test | 1 regression/month | 3 weeks | Tax changes in Q1 | Billing |
Review it quarterly, with product in the room. It turns “engineering complaining about code” into a shared list of business costs.
Pay down in the right order
- High interest, low principal first. Quick wins that remove recurring pain build trust for the bigger items.
- Debt on the critical path of the roadmap next. If the next three features all go through the same module, cleaning it is the fastest way to deliver them.
- Leave the rest. Low-interest debt can wait indefinitely. Some of it will disappear when the code is retired.
Bundle it with work people already want
The most reliable way to get debt paid isn’t a separate “debt sprint”. It’s bundling: fix the debt in the area where a feature is being built, and estimate it as part of the feature.
“Adding partial refunds to the wallet takes three weeks. Two of those weeks move the wallet to a proper ledger table. After that, the next four wallet features are days each instead of weeks.”
That’s a business case, not a request for permission. A standing allocation (many teams reserve 15–20% of capacity for engineering health) works well alongside it, for the items no feature will touch.
Stop creating it silently
Some debt is a good decision: shipping a shortcut to meet a real deadline is often right. The problem is debt taken silently. When you take a shortcut on purpose, add it to the register the same day, with the interest you expect and when you plan to pay it. Deliberate, recorded debt is a tool; forgotten debt is a trap.
Takeaways
- Describe debt by interest (what it costs every week), not by how ugly the code is.
- Keep a register with evidence, principal and owners; review it with product.
- Pay high-interest, low-principal items first, then debt on the roadmap’s critical path.
- Bundle paydown with features that touch the same code.
- When you take on debt deliberately, write it down that day.