What Is Technical Debt? How to Measure It and What to Do About It
Technical debt is the hidden cost of shortcuts taken in software development — and it compounds like financial debt. Here's how to recognise it, measure it, and make an informed decision about whether to pay it down.
Every software team we have ever worked with carries some technical debt. Most of them know it. Many of them have learned to live with it. A handful of them are quietly being destroyed by it.
Technical debt is one of those concepts that sits at the intersection of engineering and business in a way that makes it genuinely difficult to talk about clearly. Engineers use it as shorthand for anything messy in the codebase. Product managers hear it as a request for time they cannot give. Non-technical founders often have no mental model for it at all — until the day a simple feature request takes three months to deliver and no one can adequately explain why.
The financial debt metaphor is a good one, and we will return to it throughout this piece. The essential idea is this: every shortcut taken during software development creates a liability. That liability does not go away. It sits in the codebase, quietly accruing interest — slowing every future change, introducing fragility, making the system harder to understand. At some point the interest payments become the dominant cost of running the engineering team. At some point the principal becomes so large that the team is barely functional.
The goal of this guide is to give you a complete, practical picture of technical debt: what it is, where it comes from, how to measure it, and how to make rational decisions about what to do about it.
What Is Technical Debt?
The term was coined by Ward Cunningham in 1992, originally to explain why software gets harder to change over time. His analogy was deliberate: just as financial debt allows you to acquire something now and pay for it later, technical debt allows a software team to deliver functionality faster now by accepting a lower quality of implementation — and then pay the cost later, every time that code needs to be touched, extended, or fixed.
Cunningham's original formulation was actually nuanced in a way that the popular usage often flattens. He was describing a specific scenario: shipping code that works but is not yet fully understood, with the intention of going back to refactor it once you have learned more. That is a legitimate, even intelligent trade-off. The debt is deliberate and the team is aware of it.
The broader taxonomy of technical debt was later formalised by Martin Fowler in what is often called the Technical Debt Quadrant, which maps debt across two axes: whether it was taken on deliberately or inadvertently, and whether the decision was reckless or prudent.
Reckless and deliberate: The team knows the right approach and consciously chooses to ignore it. "We do not have time to write tests." "We will just hardcode this value for now." This is the kind of debt that causes the most long-term damage and is most often the result of short-term delivery pressure overriding engineering judgement.
Reckless and inadvertent: The team makes a poor decision without realising it is a poor decision. Often the result of inexperience, unfamiliarity with the domain, or lack of awareness of better patterns. "What do you mean there is a standard way to handle this? We built our own." The debt is real but was not a conscious trade-off — it was simply a mistake made in good faith.
Prudent and deliberate: The team knows the right approach but chooses a faster path with a clear intention to return and improve it later. This is Cunningham's original case. It is a legitimate business decision when made carefully and tracked properly. The debt becomes a problem if the "later" never arrives.
Prudent and inadvertent: The team made the best decision they could with the information they had at the time. Later, they learned something — about the domain, about better patterns, about the product's actual requirements — that revealed the earlier approach to be suboptimal. This is not a failure. It is how software development works. The right response is to acknowledge it and plan to address it.
Understanding which quadrant your debt falls into matters, because it shapes how you talk about it, who is responsible, and what addressing it requires.
How Technical Debt Accumulates
Technical debt does not usually arrive all at once. It accumulates in layers, each one individually defensible, collectively debilitating.
Delivery pressure is the most common cause. When a product launch, a client deadline, or an investor demo is approaching, engineering standards are the first thing to slip. Test coverage drops. Code review gets cursory. Architectural decisions that deserved a week of thought get made in an afternoon. None of these shortcuts feel consequential at the time. Over a year, they compound.
"We will fix it later" decisions that never get revisited. This is the classic case. The temporary workaround that becomes permanent infrastructure. The hardcoded configuration value that gets copied into three more places before anyone thinks to centralise it. The TODO comment that lives in the codebase for four years.
No code review, or low-quality code review. Code review is the primary mechanism by which a team maintains and improves its shared standards. When it is skipped under pressure, or treated as a rubber-stamp rather than a genuine technical conversation, the codebase gradually diverges from any coherent standard.
Outdated dependencies. Libraries and frameworks do not age gracefully. Security vulnerabilities are discovered. Performance improvements are made in newer versions. APIs change. A codebase that has not kept its dependencies reasonably current is accumulating a specific kind of risk that eventually becomes urgent when a CVE lands on a critical package.
Lack of tests. This is both a cause and an amplifier of technical debt. Untested code is harder to change safely, which means changes take longer and carry more risk, which means developers rush more and take more shortcuts, which means more technical debt. Test debt compounds on itself.
Lack of documentation. Code that no one has documented — at the architectural level, at the API boundary level, and at the complex-logic level — cannot be maintained by anyone except the person who wrote it. When that person leaves, the knowledge leaves with them.
Developer turnover. When a developer who understands a system well leaves, their institutional knowledge does not transfer automatically. What remains is code that works but is not understood, and understanding is the prerequisite for every change. Each departure, without proper knowledge transfer, increases the opacity of the system.
Signs Your Codebase Has a Technical Debt Problem
You might not have a formal debt audit yet, but the following symptoms are reliable indicators that the problem is material.
Features take disproportionately long to build. When a change that should take two days takes two weeks, the most common reason is that the existing code is so fragile, so poorly understood, or so deeply coupled that even a localised change requires navigating a large surface area of risk.
Every change breaks something else. This is often called shotgun surgery — the code is so tightly coupled that changing one thing causes unexpected failures in other places. When developers are afraid to touch certain parts of the system, that fear is meaningful data.
New developers cannot get productive. A healthy codebase has a learning curve, but a senior developer should be making meaningful contributions within a few weeks of joining. When the onboarding process involves months of learning undocumented tribal knowledge, or when new team members are explicitly warned away from certain parts of the code, the system has a structural problem.
Security vulnerabilities in old dependencies. If a dependency audit reveals packages with known CVEs that have not been updated, the team is carrying risk that will eventually become an incident. This is especially common in older Node.js, Python, and PHP codebases where the original developer is no longer around to manage upgrades.
No meaningful test coverage. When there are no tests, or the tests are so superficial that they pass regardless of whether the code actually works, every deployment is a calculated risk. Teams in this position often develop an informal freeze on changes to sensitive parts of the system.
The codebase is feared rather than understood. This is the most telling indicator of all. When engineers describe parts of their own system as "the scary bit" or "the black box that no one touches", the technical debt has already become an organisational constraint.
How to Measure Technical Debt
You cannot manage what you cannot measure. Technical debt is notoriously difficult to quantify precisely, but there are a set of metrics and proxies that give a useful, actionable picture.
Code quality metrics. Static analysis tools can surface several quantitative signals: cyclomatic complexity (how many independent paths exist through a function — high complexity means hard to test and hard to change), code duplication (copy-pasted logic that has to be changed in multiple places when requirements evolve), and code coverage (what percentage of the codebase is exercised by automated tests). None of these metrics tells the whole story on their own, but together they create a baseline you can track over time.
Static analysis tools. Tools like SonarQube and Code Climate aggregate these metrics into a single interface and provide a debt estimate — usually expressed as time-to-fix. SonarQube, for example, will tell you that your codebase has an estimated 47 days of remediation work outstanding, broken down by category. These estimates are imprecise, but they provide a directional view and a consistent way to track progress.
Dependency age audit. Run an audit against your package manifest — npm audit, pip-audit, bundle-audit depending on your stack — and look at which packages are outdated, by how much, and whether any have known security advisories. This is a concrete, actionable snapshot that can be produced in minutes.
Time-to-onboard as a proxy metric. Ask your team: how long did it actually take the last new developer to make their first meaningful contribution? If the answer is longer than four to six weeks for a senior developer, the codebase is not well-structured or well-documented. This is a qualitative measure but a surprisingly reliable one.
The bus factor. The bus factor (sometimes sanitised as the "lottery factor") asks: how many people would need to be hit by a bus before a critical part of the system becomes unmaintainable? A bus factor of one — a single person who uniquely understands a component — represents concentrated risk. When that person leaves, which they eventually will, the team loses irreplaceable knowledge. Identifying bus-factor-one components is an important part of a debt audit.
Types of Technical Debt and Their Impact
Not all technical debt is equal. The type of debt determines how expensive it is to carry and how expensive it is to fix.
Architectural debt is the most expensive kind. It accumulates when the fundamental structure of the system — how components communicate, how data flows, how the system is divided into layers — is poorly designed or has become obsolete as the product has evolved. Architectural debt cannot be fixed by refactoring a function. It requires restructuring large parts of the system, and that work is high-risk and time-consuming. The cost of carrying it compounds: every feature built on a poor architecture is harder to build and carries its own local debt.
Code-level debt is the most visible kind. Duplicated logic, poorly named variables, functions that do too many things, inconsistent patterns across a codebase. It slows developers down incrementally on every change and can usually be addressed progressively without major disruption.
Test debt is dangerous because it hides other debt. Without tests, you cannot know with confidence whether a change introduced a regression. Test debt makes every other type of debt harder to pay down, because refactoring safely requires a test harness.
Documentation debt is easy to dismiss and expensive to ignore. The cost is paid every time a new developer joins, every time a current developer returns to an unfamiliar part of the code, and every time a business stakeholder needs to understand how something works in order to make a product decision.
Infrastructure debt accumulates when cloud configurations are managed by hand rather than through code, when environments are inconsistent, when deployment processes are manual and tribal. The modern standard is Infrastructure as Code — Terraform, Pulumi, CDK — but many older systems have years of accumulated manual configuration that is impossible to audit or reproduce reliably.
Dependency debt is distinct in that it carries an external risk profile. A package you wrote cannot suddenly develop a remote code execution vulnerability. A package written by a third party can. Outdated dependencies in actively maintained software are a ticking clock.
Making the Business Case for Paying Down Technical Debt
This is where many engineering teams get stuck. The case for addressing technical debt is obvious to engineers and opaque to everyone else. Here is how to frame it.
The interest payment framing is the most useful one for non-technical stakeholders. Every month you carry a significant architectural debt, you are spending a percentage of your engineering capacity on the overhead it creates — slower feature delivery, more bugs, longer debugging cycles, more careful deployments. Estimate what that overhead costs. If your engineering team costs £50,000 per month and a reasonable estimate is that debt-related friction is consuming 30% of their productive capacity, you are paying £15,000 per month in interest. That framing makes debt reduction a straightforward ROI conversation.
The cost of doing nothing is the other side of the ledger. Technical debt does not stay constant. It accumulates interest. The codebase that is manageable today will be harder to work in next year and potentially unworkable the year after. The right framing is not "we are asking for three months to clean up the code." The right framing is "we have two options: invest three months now and recover our full delivery capacity, or do nothing and watch delivery capacity continue to decline, with the remediation work growing larger and riskier every quarter we delay."
When engaging non-technical stakeholders, quantify what you can and be honest about the uncertainty in what you cannot. A graph of feature delivery velocity over time, showing a consistent downward trend, is more persuasive than any technical argument.
Strategies for Addressing Technical Debt
There is no single right approach to paying down technical debt. The right strategy depends on how severe the debt is, how much runway the business has, and what the team's risk tolerance is.
The boy scout rule is the lightest-touch approach: leave every piece of code you touch a little better than you found it. Rename a confusing variable. Extract a repeated block into a shared function. Add a test for the logic you changed. This approach requires discipline but has zero additional overhead — the work happens within the normal flow of feature development. For teams with moderate debt and a culture of quality, it is often sufficient to hold the line and gradually improve.
Dedicated refactoring sprints — allocating a percentage of each sprint, or entire sprints periodically, to debt reduction — is appropriate when the debt is meaningful enough that it has to be attacked directly. The most common pattern is to reserve 20% of sprint capacity for technical improvements. This creates a predictable cadence without halting feature development, and it forces the team to prioritise which debt to address rather than trying to fix everything at once.
The strangler fig pattern is the right approach for architectural debt that cannot be addressed incrementally. Named after the fig tree that grows around an existing tree and eventually replaces it, the pattern involves building a new, well-structured system alongside the legacy one, gradually routing functionality to the new system until the old one can be decommissioned. This is a lower-risk alternative to a big-bang rewrite because the old system keeps running throughout the migration, and the new system can be validated incrementally.
Incremental migration versus big bang rewrite is the central strategic choice when debt is severe. In almost every case, incremental migration is safer. The big bang rewrite starts from the assumption that you can freeze the old system, build a complete replacement, and cut over at a defined moment. In practice, requirements change during the rewrite, the old system continues to accumulate changes, and the cutover moment never arrives cleanly.
When to Rewrite vs Refactor
The full rewrite is almost never the right answer. It is, however, sometimes the correct one. The conditions that justify a full rewrite are specific.
The codebase is in a language or framework that no longer has an active ecosystem, cannot be incrementally migrated, and cannot support the product's requirements even with significant refactoring. The system has been modified so many times by so many different hands that it no longer has any internal coherence, and the cost of understanding it is greater than the cost of rebuilding it. The business requirements have changed so fundamentally that the data model and the core architecture are no longer valid — not just messy, but wrong.
Even when all of these conditions are met, the rewrite should be scoped as tightly as possible. Rebuilding one component from scratch, with well-defined interfaces to the rest of the system, is categorically safer than attempting to rebuild the entire application simultaneously. "Let us do a full rewrite" should be treated as a proposal that requires serious scrutiny — because the history of full software rewrites is, frankly, not encouraging.
How We Help Clients With Technical Debt at Cyberbeak
We work regularly with businesses whose products have accumulated significant technical debt — often without the current team fully understanding how deep it runs.
Our codebase audit is a structured, time-bounded engagement designed to give both the technical team and business leadership a clear picture of where the debt sits, how much it is costing, and what it would take to address it. We review the architecture and code structure, run static analysis tooling, audit the dependency tree, assess test coverage, and interview the engineering team about the parts of the system that cause the most friction in day-to-day development. We look at the bus factor, the onboarding experience, and the deployment and infrastructure practices.
The output is a written report that avoids technical jargon where possible, gives a prioritised view of the debt by type and impact, and includes a realistic remediation roadmap. We are explicit about what we are recommending and why, and we are equally explicit when the right answer is to live with certain debt rather than pay it down — not all debt is worth addressing immediately.
Where clients want to proceed to remediation, we scope the work carefully, agree on the right strategy for each category of debt, and work alongside the existing team where possible. We believe in leaving teams more capable than we found them, which means the goal is not just to fix the code but to establish the practices that prevent the same debt from accumulating again.
FAQ
Is all technical debt bad?
No. Debt that is taken on deliberately, tracked carefully, and paid down when the conditions allow is a legitimate engineering tool. The problem is unmanaged debt — debt that accumulates without awareness, that is never tracked, and that is never addressed until it becomes a crisis. The four-quadrant framework is useful here: prudent, deliberate debt taken with the intention to refactor once you have learned more is different in kind from reckless debt accumulated by ignoring engineering standards under deadline pressure.
How long does it take to fix technical debt?
It depends entirely on the type and volume of debt, the size of the team, and the approach taken. Code-level and test debt can often be addressed progressively over weeks and months without interrupting feature delivery. Architectural debt — particularly in large, mature codebases — can take months to years to address, especially if the team is doing it incrementally alongside normal product work. We have never seen technical debt that could not be meaningfully reduced within six months of focused effort, but we have also seen debt severe enough that a complete resolution was a multi-year programme.
Can you fix technical debt without stopping feature development?
In most cases, yes. The boy scout rule and the 20% sprint allocation model both allow debt reduction to proceed alongside feature work. The exception is when the debt is severe enough that it is actively blocking feature development — in which case a focused remediation sprint is often justified, because continuing to add features to an unreliable foundation simply adds more debt at the application layer on top of the existing structural problems. The business case for a brief feature freeze is often more straightforward than teams expect once the interest-payment framing is applied.
How do we prevent technical debt in a new build?
You cannot prevent it entirely, but you can prevent it from accumulating recklessly. The fundamentals are: enforce code review as a genuine technical conversation from day one; set a test coverage baseline and hold it; keep dependencies current on a regular cadence rather than in emergency batches; write architecture decision records when significant decisions are made; treat the codebase as a shared asset that every team member is responsible for improving incrementally. Building with a clean modular structure — even in a monolith — makes the inevitable refactoring work cheaper and safer when it arrives.
If your product is showing signs of accumulated technical debt — slowing delivery, fearful developers, a codebase that has grown beyond anyone's full understanding — we are happy to have a direct conversation about what we are actually dealing with and what the realistic path forward looks like. We have done this work many times. We will tell you honestly what we find, and we will not recommend more remediation than the business case justifies.
Talk to our team about your project
We work with businesses across the UK, USA, UAE, KSA, Canada, Australia and Germany to build custom software, SaaS platforms and marketplace systems.