Every codebase past its first few months has technical debt, and every team has at least one engineer who wants to stop and fix it now, and one who wants to keep shipping features and deal with it later. Both are right some of the time. The actual skill isn’t picking a side – it’s telling the difference between debt that’s quietly costing you every sprint and debt that’s genuinely fine to leave alone.
The metaphor is more useful than the argument
Ward Cunningham coined “technical debt” to describe a real trade-off, not a moral failing: shipping the fast, imperfect version now is sometimes the right call, as long as you intend to pay it down before the interest – the ongoing cost of working around it – outweighs what you saved. Martin Fowler’s writeup on the concept is worth reading in full, particularly the point that debt taken on deliberately and debt accumulated by accident are very different situations that deserve different responses (martinfowler.com/bliki/TechnicalDebt.html).
Sort it into a quadrant before arguing about it
Fowler later extended the idea into a simple two-axis split: deliberate versus inadvertent, and reckless versus prudent. A deliberate, prudent shortcut – “we know this doesn’t handle multi-currency yet, we’ll add it when a customer needs it” – is a normal engineering decision. Reckless, inadvertent debt – code nobody understood was a problem until it broke – is the kind that deserves a real conversation about why it happened (martinfowler.com/bliki/TechnicalDebtQuadrant.html). Naming which quadrant you’re in defuses a surprising amount of the argument, because it separates “was this the wrong call” from “is this worth fixing now.”
Ask what it’s actually costing this month
Vague debt (“this module is a mess”) never wins a prioritization argument against a customer-facing feature with a deadline. Specific, recurring cost does. Track it the boring way: every time a piece of debt slows down an unrelated task – a bug takes three hours to fix because the module has no tests, a feature takes two extra days because the data model doesn’t support it cleanly – log it against that piece of debt.
# debt-log.md entry
Area: order pricing calculation
Cost this month: 2 incidents, ~9 engineer-hours
Why: discount logic is duplicated in 3 places, changes require
updating all three or a discount silently doesn't apply
Fix estimate: ~2 days to consolidate into one function
Decision: schedule for next sprint - cost is now exceeding fix estimate
Once the cost is visible in hours instead of vibes, “when to fix it” becomes an ordinary prioritization decision instead of a philosophical one.
Explaining it to people who don’t read code
A product manager or founder doesn’t need the implementation detail, but they do need the business consequence, and it’s on engineers to translate one into the other. “The discount logic is duplicated in three places” means nothing to someone prioritizing a roadmap. “We’ve had two pricing bugs reach customers this month because of how this is built, and the next feature in this area will take twice as long” is a sentence anyone can weigh against other priorities. Keep the debt log from the earlier example somewhere non-engineers can see it, not buried in an engineering wiki – it turns “just trust us” into a conversation with actual numbers.
Piggyback on feature work when you can
The easiest debt to pay down is the debt sitting directly in the path of a feature you already need to build. If you’re touching the pricing module for a new discount type anyway, that’s the moment to consolidate the duplicated logic, not six months later in a dedicated cleanup ticket that will keep losing to higher-priority work. This isn’t a substitute for the dedicated time mentioned below – some debt sits in code nobody’s touching for other reasons – but it’s the cheapest debt you’ll ever pay off, so take the opportunity when it appears.
Debt that’s fine to leave alone
Not all debt needs a plan. Code that’s ugly but stable, isolated, rarely touched, and not on a path anyone’s about to build on top of – leave it. The Agile Alliance’s definition of technical debt makes this point well: the debt itself isn’t the problem, the compounding interest is, and code nobody touches doesn’t compound (agilealliance.org/glossary/technical-debt). Refactoring code just because it offends you, with no plan to build on it soon, is time you could have spent on something that pays back.
Make room for it explicitly
The most durable fix isn’t a big cleanup sprint – it’s a standing habit. Reserve a fixed slice of every sprint, even 10-15%, for debt work chosen from the log above, ranked by cost. This keeps debt paydown from competing head-to-head against features in every single planning meeting, which is a fight debt usually loses until it’s already expensive.
The goal was never a debt-free codebase – that doesn’t exist on a real product with real deadlines. The goal is debt you chose on purpose, that you can see the cost of, and that you’re paying down faster than you’re taking it on.