Technical Debt: When Do You Clean Up in Production?
Technical debt is inevitable. Which debt do you live with, which do you pay with a sprint, when do you do a large refactor? A framework built from real production decisions.
Every production system has technical debt. Even after one day of existence. That's inevitable. The real question is about recognizing, prioritizing, and paying that debt — otherwise one day it catches you at a very bad moment.
In 14 years I've never seen a "no tech debt" system. What I've seen is the difference between well-managed debt and debt just accumulated by accident. This post tries to draw that line.
What tech debt is and isn't
Technical debt = technical choices made consciously for short-term speed, with long-term cost.
Examples (debt):
- Code written "fast now, refactor later"
- Feature merged without tests
- "Temporary workaround" that got permanent
- Un-upgraded old library version
Not examples (not debt):
- A bug (a bug is a bug, not debt)
- A missing feature (that's a gap, not debt)
- Code that's better written today than yesterday (that's hindsight wisdom, not debt)
Saying "we have a lot of tech debt" without this distinction is meaningless. Every team has problems of many kinds; debt is one specific category.
Categorizing debt
I think about debt on two axes:
1. How expensive is it to live with?
- Does it hurt every day, or once a year?
2. How expensive is it to fix?
- An afternoon, a sprint, months?
Plot them into a 2×2:
| To live / To fix | Cheap to fix | Expensive to fix |
|---|---|---|
| Cheap to live with | Fix when the chance comes | Leave it alone |
| Expensive to live with | Fix now | Plan a big refactor |
"Expensive to live / cheap to fix" is where the best ROI lives. This is the quadrant most teams miss. An hour of fixing that saves 5 minutes every day pays off in 12 working days. Do it now.
"Cheap to live / expensive to fix" — accept living with it. A 5-year refactor plan for code that never actually hurts is the fastest way to burn out a team.
When does a big refactor make sense?
A big refactor is justified in exactly two situations:
1. The current architecture can't meet the new requirement
Our case: on-prem wasn't capable of supporting cloud-native features (segmentation, canary, autoscale) without heavy custom work.
Reason: not "the old system is ugly" but "the old system can't do what we need to do now."
2. Security / risk has become serious
Unsupported framework 3 majors behind, critical security vuln, race condition that regularly reproduces in production — these justify a big refactor.
"More readable code" as the trigger is a trap. That refactor never ends.
Daily practice: small refactors
Big refactors are yearly events. Daily, small refactors are the habit:
- When writing a new feature, clean the old code in the same file
- When you see 2–3 duplicate spots, consolidate
- Spend a minute to rename for clarity
- Remove unnecessary abstraction (over-engineering is also debt, not just under-)
Boy Scout Rule: leave the code slightly better than you found it.
Are "debt sprints" a good idea?
Some teams do "tech debt sprints" — stop normal work for a sprint, pure cleanup. I have mixed feelings.
For: forces work that otherwise never happens. Good for catch-up upgrades.
Against: doesn't sustain. "One day per week for debt" kinds of ongoing practices work better than sprint-sized explosions. Every dev should be paying some debt every day.
My preference: instead of a separate sprint, carve off 15–20% of every sprint for "improvement" — debt + tooling + docs. Each sprint becomes slightly better, no big-bang resets.
What to record vs not
If you're tracking debt, don't open a JIRA epic with 100 tickets under it. 90% will never be read.
Instead:
- One one-page "known debt" list per major service
- For each: what, when added, why, priority, expected cost to pay
- Reviewed quarterly — add new, delete paid, reassess stale
That simple discipline keeps the team's shared view of debt accurate. It's the team lead's responsibility to keep the page current.
Takeaway
Technical debt is a normal part of successful software. Systems without any debt are either too slow to move or never become important.
What matters is taking debt consciously and paying it consciously. Being able to step back and see how much debt you have, and at what cost it hurts, is the skill. Teams that do this don't get hurt by debt; teams that don't end up begging to "rewrite everything from scratch" three years in.
Big or small, see the debt, name it, prioritize it, pay it. That simple loop determines a system's long-term health.
Long-form notes on distributed systems, production stability, AI-assisted shipping. No spam, easy to unsubscribe.
Comments
No comments yet. Be the first.