// Staff-level judgmentWorked on 2015–now

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.

#techdebt#engineeringmanagement#career#softwareengineering

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

  1. High interest, low principal first. Quick wins that remove recurring pain build trust for the bigger items.
  2. 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.
  3. 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.
// TL;DR, in one picture
✓ the feature we demoed// TODO: remove after migration (2018)3 feature flags stuck at 100% since 2019the cron job that quietly fixes prod dataone engineer who understands billinga table two services both write tonobody knows what this does. don't touch it.Tech debt: you only ever see the tip.ayushwing.com