Technical debt is like financial debt: taking on a little debt to hit an urgent product launch deadline is normal, but failing to pay the interest leads to eventual bankruptcy.
When minor feature updates that used to take two days now take three weeks, your software is suffering from severe technical debt.
Here is how engineering leaders diagnose and remediate legacy code bottlenecks.
Two clarifications make the rest of this article more useful. First, not all technical debt is a mistake. Deliberate, documented shortcuts taken to ship something important are a legitimate business decision. The dangerous debt is the accidental kind that nobody recorded and nobody owns. Second, debt is measured in delivery capacity, not in code aesthetics. If a codebase is ugly but features ship quickly and reliably, the debt is not yet a problem worth solving — and refactoring it is somebody's preference rather than the business's interest.
The 4 Warning Signs of High Technical Debt
- Fear of Deployment: Engineers hesitate to deploy on Fridays because minor changes trigger unexpected regressions.
- Slow Onboarding: New developers take over a month to set up local environments and submit their first pull request.
- Patch-on-Patch Architecture: Complex business logic is duplicated across multiple controllers instead of centralized in domain services.
- Outdated Dependencies: Framework versions are 3+ major releases behind, introducing unpatched security vulnerabilities.
A few more signals that show up in our audits, and usually earlier than the four above:
- Rising change failure rate. A growing share of deployments cause incidents or require an immediate fix.
- Long-lived branches. Work that sits unmerged for weeks because merging is frightening.
- Feature time trending up. The same kind of ticket took three days last year and takes nine now, with no increase in scope.
- Workarounds with names. People say "don't touch the billing module" — an unwritten rule that is really a risk statement.
- Manual release steps in someone's head. Deployments that only one person can perform safely.
- A growing bug backlog relative to features. Sustaining work quietly crowding out roadmap work.
Measuring It, So the Conversation Is Not Just Vibes
The point of measurement is to turn "the code is bad" into a number a CFO recognises. The most useful set:
| Metric | What it measures | Direction |
|---|---|---|
| Deployment frequency | Release friction | Higher |
| Lead time for changes | Commit → production | Lower |
| Change failure rate | Share of deploys causing incidents | Lower (target under 15%) |
| Mean time to restore | Recovery capability | Lower |
| Feature cycle time | How long a ticket takes end to end | Lower |
| Share of engineering time on defects and incidents | The "interest payment" | Lower |
| Dependency lag | Major versions behind on critical libraries | Lower |
| Critical-path test coverage | Safety net where it matters | Higher |
| Onboarding time to first merged PR | Onboarding friction | Lower |
| Hotspot concentration | Files changed in most bug fixes | Fewer, deliberate |
Two of those are worth dwelling on. Defect time share is the clearest financial translation: if ten engineers spend 30% of their time on bug fixes and rework, you are paying roughly three full salaries to stand still. And hotspot concentration — from combining version-control churn with defect history — identifies the specific files where debt is costing money. Ten files usually account for most of the pain, which makes remediation a concrete list rather than a vague programme.
Translating Debt Into Money
| Line | Example (10 engineers) |
|---|---|
| Average fully-loaded engineer cost | $140,000/year |
| Team cost | $1,400,000/year |
| Share of time on defects, rework and manual release work | 25% |
| Annual cost of the interest | $350,000 |
| Equivalent output lost | 2.5 engineers |
Frame remediation as buying back capacity. A $60,000 investment that returns 10% of engineering time is worth roughly $140,000 per year — an argument that survives a board meeting, where "we should refactor the controllers" does not.
The Debt Quadrant: Not All Shortcuts Are Equal
The most useful mental model for deciding what to fix and what to leave comes from Martin Fowler's quadrant, which splits debt along two axes:
| Reckless | Prudent | |
|---|---|---|
| Deliberate | "We don't have time for design" | "We must ship this quarter; we'll address the schema later — logged as a decision" |
| Inadvertent | "What's layering?" | "Now we know how we should have built it" |
The two right-hand quadrants are survivable. Prudent-deliberate debt is a business decision with a written record; inadvertent debt is what every team accumulates as it learns the domain. The left-hand quadrants are where organisations get into trouble, because there is no record and often no awareness.
The practical consequence: when you take on debt deliberately, write it down. A short decision record — what we are shortcutting, what it will cost to revisit, what would trigger the revisit — turns a future argument into a scheduled task. That single habit is the difference between managed debt and the kind that quietly compounds for years.
The 20% Rule for Continuous Refactoring
Rather than halting all product development for a 6-month complete rewrite (which often fails), allocate 20% of every sprint cycle to tech debt reduction: * Upgrading dependencies. * Increasing automated test coverage on brittle modules. * Refactoring bloated database queries.
Why 20% Instead of a Big Rewrite
Rewrites are seductive and frequently catastrophic. The reason is not that rewrites are inherently impossible; it is that the old system keeps changing while you rebuild it. Feature requests continue, integrations move, customers grow — so the target moves faster than the rebuild. Joel Spolsky's essay on this is thirty years old and still describes what we see: teams that rewrote from scratch spent years re-implementing things the old system already did.
The alternative pattern that works is the strangler fig: extract and replace one seam at a time, running old and new in parallel behind a router or feature flag, migrating in slices. We modernised a twelve-year-old Django application this way over seven months with no service interruption: the invoicing module went first (highest risk, highest return), then reporting, then the frontend, with every slice verified against the old path before switching over.
Four Ways to Fund the Work
| Approach | When it works | Risk |
|---|---|---|
| 20% of every sprint | Steady, low-risk improvement | Eroded when deadlines tighten |
| A dedicated debt sprint every 5th sprint | Visible, planned, easy to communicate | Loses momentum if skipped twice |
| A named platform team | Larger organisations, many products | Can be isolated from product reality |
| Deadline-driven exception | Security incidents, EOL dependencies, outages | Reactive; you are borrowing at a bad rate |
The 20% rule is the best default because it is continuous and survivable. The honest failure mode is that it is the first thing sacrificed when a launch slips — so make it explicit in the process, not a standing good intention.
How to Prioritise What to Pay Down
Not all debt deserves repayment. Rank candidates with two axes:
- How much it costs now — defects, incidents, developer time, customer impact.
- How much it will cost later — security exposure, dependency end-of-life, compliance, scaling limits.
Then apply three filters:
- Does it touch work we are already doing? Refactoring a module you are about to change anyway is nearly free; refactoring a module nobody touches is a hobby.
- Is there a deadline attached? A framework at end-of-life, a deprecated API, or a certificate/PCI requirement creates a real date. Those go first.
- Is it a one-way door? Data model and tenancy decisions are expensive to revisit; UI polish is not.
The output of that ranking should be a short, dated list — five to ten items, each with a measured baseline and an expected effect. Debt programmes fail when they are defined as "improve quality" rather than "cut checkout errors by half by March".
Stopping New Debt From Accumulating
Paying down debt while generating it faster is a treadmill. The guardrails that actually hold:
- Definition of Done includes tests, docs and a deploy path — no exceptions for "small" changes.
- Mandatory code review by someone other than the author.
- A dependency policy: upgrade cadence, a maximum lag behind the latest LTS, and a quarterly upgrade window booked in the calendar.
- Architecture decision records for consequential choices, so future engineers understand why.
- A no-new-technology rule unless someone owns its maintenance.
- Incident reviews that produce a change to the system, not just a note.
None of those are glamorous. Together they are the difference between debt that stays manageable and debt that compounds.
A 90-Day Plan
| Weeks | Focus | Output |
|---|---|---|
| 1–2 | Measure | DORA metrics, defect time share, dependency lag, hotspot list |
| 3–4 | Quick wins | Delete dead code, close easy vulnerabilities, fix the worst N+1s, automate the riskiest manual release step |
| 5–8 | Safety net | Integration tests on critical paths; CI gates; staging parity |
| 9–12 | Structural work | The top hotspot: extract a module, centralise duplicated logic, split a monolith endpoint |
| Ongoing | Guardrails | Definition of Done, dependency window, monthly metric review |
By day 90 you should be able to show two numbers moving: cycle time down and defect time share down. If neither has moved, the work was chosen for technical tidiness rather than business impact — which is the most common way debt programmes lose their funding halfway through.
Related reading: Automated testing is the cheapest way to stop debt accumulating; microservices vs monolith covers the architectural decisions that age badly; and DevOps and CI/CD covers the delivery pipeline that reveals debt early. Need a code audit or modernization roadmap for your legacy software? Consult with KEHEM IT.
Suspect your codebase is carrying debt you cannot see? Book a code audit and we will put a number on it.
Have a project in mind?
KEHEM designs and builds thoughtful websites, SaaS products, and business systems.