What is technical debt?
Technical debt is the accumulated cost of shortcuts, deferred work, and suboptimal decisions in a codebase. Like financial debt, it accrues interest: the longer you leave it, the more it costs to work with and around.
Some debt is deliberate. You ship a quick solution to meet a deadline, knowing you'll clean it up later. That's a reasonable trade-off if you actually go back and clean it up. The problem is that "later" often never comes, and the shortcuts compound.
Other debt is accidental. The team didn't know a better approach at the time, requirements changed and the code wasn't updated to match, or people left and their code became untouchable because nobody understands it.
Types of technical debt
- Code debt. Poorly structured code, duplicated logic, missing abstractions, inconsistent patterns. Makes changes slow and error-prone.
- Architecture debt. The system's structure doesn't match current requirements. Components that should be separate are tangled together. This is the most expensive kind to fix.
- Dependency debt. Outdated libraries, unsupported frameworks, end-of-life platforms. Security vulnerabilities pile up and upgrades become progressively harder.
- Test debt. Missing automated tests, unreliable test suites, manual-only testing. Every change carries risk because you can't verify it didn't break something.
- Documentation debt. No one knows how the system works except the person who built it. When that person leaves, the knowledge goes with them.
- Infrastructure debt. Manual deployments, missing monitoring, no disaster recovery. The system runs, but operating it requires heroics.
Measuring technical debt
You can't manage what you can't measure. But technical debt isn't easily reduced to a single number. Instead, measure its impact through proxy indicators:
Velocity indicators
- Time to deliver a feature. How long does it take to ship a straightforward change? If simple features consistently take weeks, debt is the likely cause.
- Bug rate. How many bugs ship per release? Rising bug rates signal code quality issues.
- Onboarding time. How long until a new developer is productive? If it takes months, the codebase is too complex or poorly documented.
Risk indicators
- Incident frequency. How often does the system fail? Production incidents tied to fragile code or infrastructure point at debt.
- CVE exposure. How many known security vulnerabilities exist in your dependencies? Tools like Snyk, Dependabot, or npm audit can quantify this.
- Single points of failure. Components or knowledge held by one person. If they're unavailable, nothing moves.
Cost indicators
- Developer hours spent on workarounds. Time spent working around problems rather than solving them.
- Maintenance vs feature ratio. What percentage of engineering time goes to keeping things running vs building new capabilities? Above 40% maintenance is a warning sign.
Assessment framework
A structured assessment looks at each area of debt and scores it on severity and impact:
| Area | Questions to ask | Severity (1-5) |
|---|---|---|
| Code quality | Is the code readable, consistent, and well-structured? | |
| Architecture | Does the structure support current and near-future requirements? | |
| Dependencies | Are libraries current? Any known vulnerabilities? | |
| Testing | Is there automated test coverage? Do tests run reliably? | |
| Documentation | Can someone new understand the system from docs alone? | |
| Infrastructure | Are deployments automated? Is monitoring in place? | |
| Security | Are auth, encryption, and access controls current? |
Score each area 1-5 (1 = minimal debt, 5 = critical). Then multiply by business impact (how much this area affects delivery speed, reliability, or risk) to prioritise where to invest effort.
Building the business case
Technical leaders often struggle to get budget for debt reduction because they frame it in technical terms. "We need to refactor the order module" means nothing to a CFO.
Reframe in business terms:
- "Feature delivery will take 3x longer by next year if we don't address the architecture issues."
- "We have 47 known security vulnerabilities in production. A breach would cost us $X in incident response and reputation damage."
- "Our developers spend 30% of their time on workarounds. That's $Y/year in wasted engineering salary."
- "We can't integrate with the new payment provider until we update the framework. That's blocking $Z in revenue."
Attach numbers wherever possible. Estimates are fine. "Roughly $150K/year in lost productivity" is more persuasive than "the code is hard to work with."
Prioritisation
You can't fix everything at once. Prioritise based on:
- Security debt. Known vulnerabilities, unsupported dependencies, missing access controls. Fix these first. The risk is concrete and potentially catastrophic.
- Blocking debt. Debt that prevents you from doing something the business needs. Can't add a feature, can't integrate a new system, can't scale to handle load.
- Expensive debt. The things that cost the most in developer time and incident response. Use your measurements to identify these.
- Strategic debt. Debt you're choosing to defer because the cost of fixing it outweighs the benefit right now. Track it so it doesn't become a surprise later.
Managing debt ongoing
Debt is normal. Zero debt is neither achievable nor desirable (it would mean you never ship anything fast). The goal is managed debt: you know what you owe, you're paying it down at a sustainable rate, and it's not blocking progress.
- Allocate 15-20% of engineering time to debt reduction. Not a dedicated sprint. Fold it into regular work. Every feature story can include a small debt reduction task.
- Track debt as backlog items. They deserve the same visibility as feature requests. If debt lives only in developers' heads, it never gets prioritised.
- Review quarterly. Has the debt gotten better or worse? Are the measurements trending in the right direction?
- Celebrate reduction. Bug rate dropped 30%. Deploy time went from 4 hours to 15 minutes. These matter. Make them visible.
FAQ
Is some technical debt acceptable?
Absolutely. Deliberate debt taken to ship faster is a valid engineering decision. The key words are "deliberate" and "tracked". If you know you're taking a shortcut, document it and schedule the fix. Problems arise when debt accumulates silently.
How do I convince leadership to invest in debt reduction?
Quantify the cost of inaction. Show how debt slows feature delivery, increases incident rates, or blocks strategic priorities. Frame it as risk reduction and productivity improvement, not "cleaning up code."
Should I do a big refactoring project or pay down debt incrementally?
Incrementally, almost always. Big refactoring projects are risky, hard to justify, and often get cancelled partway through when business priorities shift. The "boy scout rule" (leave the code better than you found it, every time you touch it) is more sustainable and less disruptive.
When is it time to replace rather than fix?
When the cost of maintaining the existing system exceeds the cost of building or buying a replacement. If debt is so deep that every change is painful and risky, a legacy migration may be the better investment.
Key takeaways
- Technical debt is not always bad. Deliberate debt taken for speed is a valid trade-off. Accidental debt from neglect is the problem.
- Measure debt by its impact: how much does it slow the team, how often does it cause incidents, and what does it block?
- Build the business case in business terms, not technical ones. "This costs us 15 hours/week in developer time" beats "the code needs refactoring".
- Pay down debt incrementally alongside feature work. Dedicated "debt sprints" rarely get approved and never last.