Skip to content

Tech-Debt Management

Key Points

  • "The code is messy" is not a tech-debt argument. Cost in hours + risk in dollars is.
  • Maintain a TD ledger — a versioned, prioritized list with cost, risk, and remediation effort. Treat it as a product artifact.
  • Fowler's quadrant (deliberate/inadvertent × prudent/reckless) tells you how the debt got there, which dictates the how to pay it down.
  • Sustainable engineering budget (~15-20% of capacity) is more durable than "debt sprints".
  • Tools (SonarQube, NDepend) are inputs, not roadmaps. A 4,000-issue dashboard prioritizes nothing.
  • Connect every major paydown to an ADR. Decisions outlive sprints.
  • Rewrites are almost never the answer. Strangle, refactor, or live with it.

Concepts (deep dive)

Why tech-debt narratives fail

The classic engineer pitch: "we need to refactor X." It dies in the priority meeting because: - No quantified cost ("a few weeks"). - No business consequence ("it's messy"). - No alternative considered. - No owner; nobody is accountable for outcomes.

PMs and EMs aren't villains — they're being asked to choose between concrete features with revenue projections and vague engineering grievances. Make TD decisions look like product decisions.

Fowler's tech-debt quadrant

Reckless Prudent
Deliberate "We don't have time for design." "We ship now and refactor next quarter."
Inadvertent "What's layering?" "Now we know how we should have done it."

Why it matters: - Deliberate-prudent — pay it down on schedule; it's a loan, not a default. - Deliberate-reckless — culture problem; coaching, not refactoring, fixes it. - Inadvertent-prudent — most common in healthy teams; pay it down as understanding matures. - Inadvertent-reckless — skills gap; mentor and review, don't just refactor.

Same code mess, four different responses.

The TD ledger

Treat tech debt like a real backlog:

ID  | Title                          | Cost (eng hrs)| Risk (1-5) | Effort (eng hrs)| Owner | Status
TD-1| Audit log async-over-sync      |     ~8/sprint |     4      |       40        | Alex  | In progress
TD-2| Manual deploy steps for API    |     ~4/sprint |     5      |       20        | Sam   | Open
TD-3| EF migrations bypassed in 2 svcs|    ~0/sprint |     5      |       80        | TBD   | Open
TD-4| Duplicated DTO mappers (×6)    |     ~2/sprint |     2      |       30        | Riley | Open

Required columns: - Cost = engineering time being lost right now (per sprint, per incident, etc.). - Risk = impact × likelihood if left alone. - Effort = honest remediation estimate. - Owner = a name. "TBD" is the silent killer.

ROI = cost saved per sprint × time horizon ÷ effort. Now you can compare TD to features.

Prioritization frameworks

  • ICE (Impact, Confidence, Ease, 1-10 each, multiply). Fast and fuzzy; good for triage.
  • RICE (Reach × Impact × Confidence ÷ Effort). Better when impact is uneven across users.
  • Cost-of-Delay (value lost per week not fixed). The CFO's framing; powerful in roadmap meetings.
  • Risk × Probability matrix (the heatmap). Best for security or compliance debt.

Pick one and apply consistently — comparability matters more than which framework.

The sustainable engineering budget

Carve out a fixed % of capacity (commonly 15-20%) for engineering health work: tech debt, tooling, observability, performance, dependency upgrades.

Why this beats "debt sprints": - Continuous compounding. A little every sprint > one big push that gets cut when product panics. - Less negotiation overhead. It's already in the plan; no quarterly battle. - Better signal. If the budget is regularly underused, debt isn't really hurting; if it's regularly overrun, something's structurally broken.

The hard part: defending the budget when product pressure spikes. The answer is the ledger — show the cost of skipping a quarter.

Communicating TD to non-eng stakeholders

❌ "The code is messy; we need to refactor." ❌ "We have a lot of technical debt."

✅ "We're losing 12 engineer-hours per sprint to manual deployment steps for the billing API. Automating costs 30 hours once. Payback is 2.5 sprints."

✅ "The audit log writes synchronously, which adds 200ms p99 to every checkout. At current traffic, we lose ~$X/month in conversion. Async fix is 1 week."

Frame in time, dollars, or risk — never in code aesthetics.

The Boy Scout rule debate

"Always leave the campground cleaner than you found it." — Robert C. Martin

✅ For: organic improvement, no schedule overhead, scales with team size. ❌ Against: opportunistic rewrites bloat PRs, introduce risk, dodge prioritization.

Pragmatic stance: bounded Boy Scouting. Yes to renaming a confusing variable in the file you're editing. No to refactoring the surrounding 400 lines because you're already there. Larger cleanups → ledger → planned work.

Connection to ADRs

Every significant paydown is a decision worth recording:

  • ADR-N: Migrate audit log to async pipeline — the why lives forever, the next person doesn't unwind it accidentally.
  • ADR-M: Sunset legacy mapping layer — even an "obvious" deletion benefits from a one-page record.

The ADR log + the TD ledger together form your engineering health story.

Tools — inputs, not outputs

  • SonarQube / SonarCloud — code smells, hotspots, duplication. Use the Hotspots view, not the issue count; bare issue counts have no priority.
  • NDepend — dotnet-specific dependency, coupling, cyclomatic complexity analysis. Excellent for "where is the rot concentrated?" trend reports.
  • dotnet format / Roslyn analyzers — catch the boring stuff in CI; never have a human comment on style.
  • Code climate / Codacy — similar, language-agnostic.
  • Architecture fitness functions (ArchUnit-style, NetArchTest in dotnet) — assert architectural rules in tests; debt that violates them fails CI.

The mistake: treating the tool's output as the roadmap. The tool tells you what; you and the ledger decide why and when.

Anti-patterns

  • The big rewrite. Joel Spolsky's classic essay still applies. Strangle instead.
  • The all-debt sprint (or quarter). Productivity collapses, morale tanks, debt re-accrues during the next feature scramble.
  • The no-debt-allowed culture. Equally toxic; ships nothing. Some debt is fine — debt you don't acknowledge is the problem.
  • The hero refactor. One senior rewrites a subsystem in a weekend; nobody else can review it; merges anyway. Now it's their code; bus factor of one.
  • Treating dependency updates as "tech debt" — they're maintenance; budget them separately so they don't compete with structural work.

Patterns

  • TD ledger as living artifactIntent: versioned list (in repo or wiki) of debt items with cost/risk/effort/owner. Reviewed quarterly; items closed or re-scored.
  • Sustainable engineering budgetIntent: reserved % of each sprint for health work; non-negotiable except in declared emergencies.
  • Strangler fig refactorIntent: route new behavior through new code; gradually migrate callers; delete old code last. Avoids big-bang rewrites.
  • Fitness functionsIntent: automated tests asserting architectural rules (layering, dependency direction). Prevents new debt of known shapes.
  • Paydown-with-featureIntent: when a feature requires touching debt-heavy code, scope the paydown into the feature estimate. Debt dies where it hurts.

Pros & cons / trade-offs

Approach Pros Cons
Continuous budget (15-20%) Compounding, predictable, low friction Easy to skip under pressure
Dedicated debt sprints Visible focus, big wins Productivity dip, debt re-accrues
Boy Scout rule Organic, no overhead Bloated PRs, scope creep
Big rewrite "Clean slate" High risk, market gap, usually fails
Strangler fig Low risk, incremental Slow, requires discipline
Fitness functions Prevents regressions automatically Up-front cost, can be brittle

When to use / when to avoid

Use the ledger any team > 3 engineers or > 1 year of code; without it, debt discussions are vibes. ✅ Use the sustainable budget for steady-state teams. ✅ Use strangler fig for legacy systems large enough that rewrite is tempting. ✅ Use fitness functions when architectural rules matter and reviews keep missing them.

Avoid debt sprints unless the org is in declared "stabilization mode". ❌ Avoid big rewrites unless the existing system is genuinely unsalvageable (and even then, doubt yourself). ❌ Avoid debt-tracking tools without owners — a 4,000-issue Sonar dashboard is decoration.


Senior-level tips

  • 💡 Translate debt into the CFO's language. Dollar cost per month, hours lost per sprint, customer-impact risk percentage. Engineers don't decide budgets; finance does.
  • 💡 The ledger is a leadership artifact, not a JIRA query. Curate it. Keep it under 30 items at any time.
  • 💡 Score risk against the next product bet, not today's reality. "If we double traffic next year, this debt becomes a P0."
  • 💡 Postmortems often surface high-priority debt. Make sure incident action items feed the ledger.
  • 💡 Pair debt fixes with feature work when scoping. "This feature plus the dependency upgrade it forces" is one estimate.
  • 💡 Don't moralize about debt. Past decisions were rational at the time. Focus on the cost going forward, not blame for the past.
  • 💡 Watch for "debt" that's actually a feature gap. Sometimes the system is fine; the requirements changed. Don't refactor when you should rebuild.
  • 💡 Track debt-velocity as a team health metric: items added vs items closed per quarter. Trend matters more than absolute count.

Common pitfalls

  • ⚠️ Vague debt items ("the auth code is bad") with no cost or owner.
  • ⚠️ Ledger that nobody owns or reviews — turns into a graveyard.
  • ⚠️ Conflating bug backlog with debt backlog. Bugs are defects; debt is design.
  • ⚠️ Letting the SonarQube count drive priorities instead of the ledger.
  • ⚠️ Approving a "small refactor" PR that grew to 2,000 lines. Send it back; it's a separate ledger item now.
  • ⚠️ Skipping the sustainable budget "just this quarter" — it always becomes "just this year".
  • ⚠️ Pitching debt as a moral imperative ("we must fix this"). It's a business decision; pitch it that way.
  • ⚠️ Celebrating a debt-paydown sprint while ignoring the new debt taken on by the feature sprint that follows.

Further reading