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 artifact — Intent: versioned list (in repo or wiki) of debt items with cost/risk/effort/owner. Reviewed quarterly; items closed or re-scored.
- Sustainable engineering budget — Intent: reserved % of each sprint for health work; non-negotiable except in declared emergencies.
- Strangler fig refactor — Intent: route new behavior through new code; gradually migrate callers; delete old code last. Avoids big-bang rewrites.
- Fitness functions — Intent: automated tests asserting architectural rules (layering, dependency direction). Prevents new debt of known shapes.
- Paydown-with-feature — Intent: 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
- Martin Fowler — TechnicalDebt and TechnicalDebtQuadrant
- Ward Cunningham — The WyCash Portfolio Management System (the original "debt" metaphor, 1992)
- Joel Spolsky — Things You Should Never Do, Part I (against the rewrite)
- Michael Feathers — Working Effectively with Legacy Code
- Neal Ford et al. — Building Evolutionary Architectures (fitness functions)
- ThoughtWorks Tech Radar — Architectural fitness function
- Atlassian — Managing technical debt
- Google re:Work / Tech Debt Talk by Carmen Hernández Andoh
- Camille Fournier — The Engineer/Manager Pendulum (search "tech debt")