How to Reduce Technical Debt
To reduce technical debt without halting delivery, pay it down incrementally in the running system, not in a stop-the-world rewrite. Establish a test safety net first, refactor behind it, apply the boy-scout rule to high-traffic code, and use the strangler pattern to replace large debt-heavy components piece by piece. Repayment is continuous maintenance, not surgery.
Part 8 gave us the operating system — how to make debt visible, prioritize it, fund it, govern it, and review it. The framework decides which debt to repay and that repayment is funded. It does not say how the repayment is actually done. This part does, and it carries the hardest constraint in the whole series: you have to pay the debt down while the system keeps running. The business does not stop needing the software so engineering can clean it up.
That constraint rules out the approach most teams reach for first — the rewrite — and points toward a different principle: debt reduction is continuous maintenance, not surgery. You repay in small, safe increments inside the living system, not in one heroic stop-the-world push. The techniques below are ordered roughly the way you should apply them.
Find the highest-interest debt first
Not all debt is worth repaying, and the debt that hurts most is rarely the debt that looks worst. Part 8 sets the governance for choosing what to repay. Standing in the codebase, the fastest way to find the debt that is actually costing you is to cross two signals you already have: how often a file changes, and how hard it is to change. Pull change frequency from version control, meaning how many commits have touched each file over the last six to twelve months, and pair it with a rough complexity read from length, nesting depth, or a linter’s complexity score. The files that are both high-churn and high-complexity are your hotspots, and that intersection is where debt charges real interest, because you pay the maintenance cost again every time you go back in.
This is why genuinely ugly code that no one ever touches can be safe to ignore, while a plain-looking module edited every week can be the most expensive thing you own. Rank the hotspots and you have a repayment queue ordered by what the debt actually costs, not by what offends you most in review. Repay from the top and stop when the interest stops hurting.
First, the safety net: tests
Before refactoring a single line, establish tests around the code you intend to change. This is not optional sequencing — it is the precondition that makes every other technique safe. Part 2 identified test debt as the force multiplier; reducing other debt without addressing test debt first is changing code you cannot verify, which is how debt-reduction efforts introduce the very defects they were meant to prevent.
The real difficulty is that debt-heavy code is often the hardest to test — that is part of why it is debt. The technique for this is the characterization test: rather than testing what the code should do, you write tests that capture what it currently does, pinning the existing behavior in place. Now you can refactor with a tripwire: if a test breaks, you changed behavior, and you can decide whether that change was intended. This is the same discipline as proving functional equivalence in a modernization — you hold the new version to the behavior of the old, because the behavior is the spec, not the documentation.
Refactor behind the tests, in small steps
With a safety net in place, refactoring is the workhorse of debt reduction: changing the structure of the code without changing its behavior, so the same function does the same thing more cheaply to maintain. Extract the tangled method, name the thing the variable actually holds, collapse the duplication into one place, untangle the dependency.
Two rules keep refactoring from becoming its own risk. Keep the steps small — many tiny, individually-safe transformations, each verified by the tests, rather than one large rewrite-in-disguise. Small steps stay reversible and stay reviewable. Separate refactoring from feature work in the commit history, even when they happen in the same sitting, so a reviewer can see that the refactor changed no behavior and the feature changed no structure. Mixing them is how a “simple cleanup” smuggles in a bug nobody can find later. Refactoring repays the code debt of Part 2 directly, and it is the most routine, lowest-risk repayment there is — once the tests exist.
The boy-scout rule: repay where you already work
The single most sustainable debt-reduction technique requires no separate project at all: leave each piece of code a little better than you found it. When you touch an area for a feature or a fix, make a small, bounded improvement to it on the way through — a clearer name, an extracted function, a missing test.
This works because it concentrates repayment exactly where Part 8’s prioritization said it should go. The code you touch is, by definition, the high-change-frequency code — the high-interest debt. The boy-scout rule pays down the expensive debt as a side effect of normal work, continuously, without a funding fight or a roadmap freeze. It will not clear a large architectural problem, but for the steady accumulation of code debt it is the most efficient mechanism there is, because the cost is amortized into work that was happening anyway.
Fund the larger repayments with a standing budget
The boy-scout rule pays for itself because it rides along with work already happening. The larger items do not. Extracting a shared module, unpicking a circular dependency, replacing a subsystem: these need their own capacity, and if that capacity is not protected it never materializes, because there is always a feature more urgent than a refactor.
The mechanism that works is a standing debt budget, a fixed fraction of every cycle reserved for repayment and allocated whether or not a deadline looms. Many teams land somewhere around 15 to 20 percent of engineering capacity, though the right number is whatever keeps your hotspot list shrinking rather than growing. Two things make or break it. Protect it, because the budget only earns its keep if it survives the sprint where everything is on fire, which is exactly the sprint the debt is making worse. And aim it at the ranked hotspots rather than at whatever an engineer finds annoying that week, so the funded capacity buys down the debt that is genuinely charging interest.
The catch is that this capacity is visibly not shipping features, and that is a real cost a leader has to defend. The defense is that the alternative is not more features but a velocity that quietly decays until the same features cost twice as much. A budget spends a predictable slice now to avoid an unpredictable tax later.
For debt too big to refactor: the strangler pattern
Some debt is past refactoring. A whole subsystem with a rotten architecture, a component built on a dead framework, a module so entangled that no small step improves it — this is the architectural debt that Part 2 flagged as the most expensive, and you cannot incrementally tidy your way out of it. The instinct is to rewrite it. Resist that instinct.
The reliable technique is the strangler pattern: build the replacement alongside the old component, route a small slice of functionality to the new path, verify it behaves identically, then gradually shift more until the old component carries nothing and can be retired. The name comes from the strangler fig, which grows around its host until it can stand on its own. A strangler facade sits in front, directing traffic, so the cutover for any given slice is a gradual, reversible traffic shift rather than a single switch.
The advantages over a rewrite map exactly onto the constraint this part opened with:
- The system keeps running the entire time — old and new coexist, so there is no roadmap freeze and no big-bang cutover date the business is betting on.
- Each slice is small and verifiable, held to the behavior of the part it replaces, so undocumented behavior is preserved rather than lost in translation.
- Repayment is reversible — if a slice misbehaves, you shift traffic back, rather than unwinding months of work.
This is the heart of slice-by-slice modernization, and it is why incremental beats big-bang for debt that has grown into legacy.
Why not just rewrite?
The rewrite deserves a direct answer, because it is the most expensive way debt-reduction fails. A full rewrite freezes the roadmap while it runs, runs longer than anyone forecasts, and — most dangerously — discards the undocumented behavior the old system accumulated over years, the knowledge debt that lives nowhere but the running code. The new system passes its tests, ships, and then surfaces the behavior nobody wrote down, as a wave of production incidents.
The failure here is structural, not a matter of team skill — which is why even strong teams’ rewrites founder. The base rate is sobering: BCG found in September 2023 that up to 70% of digital transformations fail to deliver on their objectives. Big-bang debt reduction sits squarely in that population. Incremental replacement exists to change which side of that number you land on, by removing the single point of failure — the cutover — that the rewrite bets everything on.
This is not an absolute prohibition. A small, well-understood, well-tested component with low risk can sometimes be rewritten cleanly in a sprint. The rule scales with size and risk: the larger and less understood the component, the more incremental the approach must be. We cover the full reasoning in why modernization projects fail.
Which technique, when
These are not alternatives, they are a stack you apply together. Tests come first regardless, because everything else is unsafe without them. From there, match the technique to the size of the debt and the interest it charges.
| Technique | Best for | Main cost |
|---|---|---|
| Boy-scout rule | Steady code debt in the areas you already touch | Almost none, it rides along with normal work |
| Refactoring behind tests | Tangled but salvageable code, improved in small steps | Engineering time, protected by the debt budget |
| Debt budget | Larger items the boy-scout rule cannot reach | Visible capacity not spent on features |
| Strangler pattern | Architectural debt past refactoring, whole rotten subsystems | Running old and new in parallel, plus the facade |
Reach for the cheapest technique that will actually clear the item, and escalate only when it will not.
Stop creating debt faster than you repay it
Every technique so far repays existing debt. None of it matters if the team adds debt faster than it clears it, which is the quiet reason so many cleanup efforts feel like bailing a boat that is still taking on water. Reduction and prevention are one program, not two.
Prevention does not mean never taking on debt. The useful distinction is between debt taken on deliberately and debt taken on by accident. Deliberate debt is a decision. You choose to skip a test or hard-code a case to hit a date, and you know you are doing it. That kind is fine, as long as it is recorded where the team can see it, so it gets repaid on purpose rather than surfacing later as a surprise. The debt that compounds silently is the inadvertent kind, the shortcut nobody flagged and the pattern copied without understanding. Our piece on where technical debt comes from covers the origins, but the point here is that most inadvertent debt is preventable at the moment it is written, and far cheaper to prevent than to repay.
Two low-cost gates catch most of it. A definition of done that includes tests and basic hygiene keeps the most common inadvertent debt from shipping at all. And a code review that asks one extra question of every change, does it leave the code easier or harder to change next time, turns each merge into a small prevention checkpoint. Neither is a heavy process. Both work because they act at the one moment prevention is cheap, before the debt is written down and copied.
Know whether it is working
Debt reduction you cannot measure is indistinguishable from debt reduction you are not doing, and it is the first thing cut when budgets tighten, precisely because no one can show it paid off. The trap is trying to measure the debt directly. A single debt score from a static-analysis tool is easy to game and rarely tracks what the debt actually costs.
Measure the symptom instead. What debt steals is the ability to change the system quickly and safely, so track that: how long a change to a hotspot module takes to go from started to shipped, and how often changes there cause a failure. The DORA delivery metrics, lead time for changes and change-failure rate, are a ready-made version of exactly this, and they are honest in a way a debt score is not, because they measure the outcome you care about rather than a proxy for it. Watch them on the specific modules you are repaying, not just the org-wide average, or real local improvement disappears into the noise.
The simplest leading indicator is the hotspot list itself. If the files that are both high-churn and high-complexity are leaving that list faster than new ones join it, repayment is winning. If the list keeps growing, you are repaying slower than you are borrowing, and the prevention gates above matter more than any single refactor.
How ModernLift approaches large-scale debt
When debt has compounded into a system that is genuinely legacy — past refactoring, past in-house capacity to unwind — paying it down becomes a modernization program, and the principles above scale up directly. ModernLift’s approach is the strangler pattern run as a discipline: AI-accelerated analysis reads the codebase and captures the undocumented behavior and domain rules as living specs before anything moves, so the knowledge debt is recovered rather than lost. Work proceeds slice by slice, each slice held to validated behavior parity with the legacy component it replaces and promoted only behind a strangler facade with rollback prepared, so the business keeps running the entire time. It is the same maintenance-not-surgery principle this part has argued for, applied to debt at the scale where it has become a strategic problem rather than a backlog item.
Where incremental repayment doesn’t reach
Incremental reduction is the right default, but it is not free and not always sufficient. It carries the operational overhead of running old and new in parallel and building the facade — a real cost, justified by the risk it removes, but a cost. And some debt genuinely cannot be reduced incrementally: a foundational data-model decision, a platform-level choice that everything depends on, may require a larger coordinated change no boy-scout rule will reach. The techniques here are a strong default, not a universal law. The judgment is to reduce incrementally wherever you can, to reserve the larger, riskier moves for the debt that truly requires them, and — per Part 8 — to leave alone the debt that charges no interest at all. Reducing debt that costs nothing is not a virtue; it is the same waste in a different direction.
Where this leads
We can now pay debt down in a running system. But the framework (Part 8) and these techniques both rest on a claim we have asserted more than proven: that debt meaningfully slows a team, and that reducing it meaningfully speeds one up. For a leader deciding whether any of this is worth funding, that causal link is the crux. Part 10, Technical Debt & Engineering Velocity, examines exactly how debt drags on delivery speed — and what the evidence says about the velocity you get back when you pay it down.
Frequently asked questions
- What is the best way to reduce technical debt?
- Pay it down incrementally in the running system. The most reliable sequence is to establish tests around the code you intend to change, refactor behind that safety net in small steps, and concentrate effort on the high-interest code you touch most. For large debt-heavy components, replace them piece by piece using the strangler pattern rather than rewriting all at once.
- Should you rewrite a system to remove technical debt?
- Almost never as a big-bang rewrite. Full rewrites are how debt-reduction efforts most often fail — they freeze the roadmap, run long, and risk losing undocumented behavior the old system quietly depended on. The structural failure rate of large all-at-once transformations is high, which is why incremental replacement behind a strangler facade is the safer path for debt that has grown beyond refactoring.
- How do you reduce technical debt without stopping feature work?
- By making repayment part of feature work rather than a separate project. The boy-scout rule — leave each area a little better than you found it — pays down debt exactly where you are already working. A protected capacity allocation funds the larger items, and incremental replacement keeps the system delivering throughout. Reduction and delivery proceed together; that is the whole point of doing it incrementally.
- How much time should a team spend paying down technical debt?
- Reserve a standing fraction of every cycle for repayment rather than waiting for a quiet week that never comes. Many teams land somewhere around 15 to 20 percent of engineering capacity, but the right number is whatever keeps your list of high-cost hotspots shrinking rather than growing. The part that actually matters is protecting that capacity when a deadline looms, because the sprint where everyone wants to raid the debt budget is usually the sprint the debt is making worse.
- How do you measure technical debt reduction?
- Measure the symptom rather than trying to score the debt directly. A single debt score from a static-analysis tool is easy to game and rarely tracks real cost. Instead, track how quickly and safely you can change the modules you are repaying, using delivery metrics like lead time for changes and change-failure rate, and watch whether your list of high-churn, high-complexity hotspots is shrinking. If that list keeps growing, you are borrowing faster than you repay.
- How do you stop accumulating new technical debt?
- Make debt a deliberate, recorded decision instead of an accident. Taking debt on purpose to hit a date is fine, as long as it is written down and repaid on purpose. The debt that compounds is the kind nobody flagged. Two cheap gates catch most of it. First, a definition of done that includes tests and basic hygiene. Second, a code review that asks whether each change leaves the code easier or harder to change next time.