Technical Debt Triage: What to Fix vs. What to Live With
Not all technical debt deserves immediate attention. Learn a practical framework for prioritizing what drains your velocity and what you can safely defer.
Technical Debt Triage: What to Fix vs. What to Live With
Your codebase has accumulated it: legacy patterns, outdated dependencies, tests that flake intermittently, database migrations that nobody fully understands. Technical debt isn't a binary state—some of it actively sabotages your team's productivity, while other pieces quietly coexist with your shipping code. The real problem isn't the debt itself; it's treating every issue as equally urgent.
At LavaPi, we've watched engineering teams spin their wheels on debt that doesn't matter while ignoring friction that compounds daily. The difference between teams that ship and teams that stall comes down to one skill: ruthless triage.
Map Your Debt Clearly
Before you can triage, you need visibility. Start by cataloging what you actually have.
Automated detection
Tools scan faster than humans. Use them to establish baseline metrics:
bash# SonarQube for code quality issues scan-project --include-debt-metrics # Dependency audits npm audit --json > debt-report.json pip list --outdated
Manual inventory
Automation misses the qualitative stuff. Schedule a 30-minute team session:
- Which code paths make developers groan?
- Where do bugs cluster?
- Which tests are flaky or slow?
- What would you rewrite if you had two weeks?
Document this in a simple spreadsheet. You need rough accuracy, not perfection.
The Triage Matrix: Impact vs. Effort
Evaluate each debt item across two axes. Plot it honestly.
High impact, low effort → Fix now
These are your quick wins. A single slow query that blocks every deployment, a dependency with known vulnerabilities, a frequently-used function with confusing logic:
typescript// Before: Confusing parameter overloading function processUser(id: number, opts?: {sync?: boolean; cache?: boolean}) { if (opts?.sync) { return syncUser(id); } // Unclear path for cache-only mode } // After: Clear intent function processUser(id: number, options: ProcessOptions) { return options.mode === 'sync' ? syncUser(id) : cacheUser(id); }
Low impact, low effort → Schedule and batch
Small cleanups. One-off refactors. Nice-to-haves. Group these into a "debt sprint" once per quarter. Don't let them interrupt active feature work.
High impact, high effort → Break into pieces or defer
Major rewrites and architecture shifts. Can you decompose this?
python# Instead of: "Rewrite entire ORM layer" (8 weeks) # Try: Three-sprint plan # Sprint 1: New query builder alongside old one # Sprint 2: Migrate 30% of queries # Sprint 3: Migrate remainder, remove old code class NewQueryBuilder: def __init__(self): self.fallback = LegacyORM() # Coexist during transition
Breaking it up keeps your roadmap moving and lets you validate improvements incrementally.
Low impact, high effort → Live with it
The code that works, nobody touches, and the cost of fixing exceeds the benefit. Stop feeling guilty about this quadrant. Every system has it. The goal isn't perfection; it's reducing friction on your actual workflow.
Make It a Ritual, Not a Crisis
Triage once per quarter. Spend 90 minutes as a team:
- Review metrics from your last quarter (deploy frequency, test duration, flake rates).
- Add new debt items discovered since last review.
- Re-score existing items—what was "high effort" might be lower now.
- Commit 15–20% of sprint capacity to high-impact, low-effort fixes.
This prevents debt from becoming a pile of excuses and keeps your system responsive to change.
The Bottom Line
Technical debt is a negotiation, not a fight. Some of it you eliminate. Some of it you manage. The teams that win are those who know the difference and act accordingly. Stop pretending you'll fix everything. Start being intentional about what actually matters.
LavaPi Team
Digital Engineering Company