The operational test
The wrong question to ask about technical debt is "how long has this code been here?" The right question is different: "does it slow down your ability to change the system in the context you're in right now?" If the answer is no, it isn't technical debt in the operational sense of the term. If the answer is yes, it is — regardless of when it was written, by whom, or with what intention.
This test changes the frame considerably. Technical debt isn't a moral judgement on past code; it's a measure of resistance to change in the present. A system written ten years ago, in technologies nobody uses any more, might not be debt at all if the team knows it well and can change it without friction. A system written six months ago by competent developers can be heavy debt if its modules are so coupled that every change has unpredictable consequences.
The spectrum: from micro to systemic
Technical debt isn't a uniform phenomenon. It shows up at different levels with different impacts, and confusing them means stepping in at the wrong place.
At the most granular level there's measurable debt: every small change takes longer than expected because functions carry too many responsibilities, nesting runs deep, behaviours overlap. In technical terms, that's high cyclomatic complexity; in operational terms, it means the same change that takes an hour elsewhere takes a day here, because it's hard to work out where to touch the code without breaking something else. Tools such as SonarQube track it continuously. The advantage of this form of debt is that it's visible and quantifiable: you can measure the trend over time and step in before it sets in for good.
One level up is debt from systematic violation of coding standards. Not one developer's personal style — that's preference, not debt. The problem is a lack of shared conventions that has built up over time: every developer who comes into the area has to rebuild context from scratch because the implicit rules change from file to file. The cost isn't technical but cognitive: it increases reading time, makes it harder for the team to reason together, and slows down code review.
At the module level comes debt from excessive coupling: a component that depends on too many others, or that's called from too many parts of the system, becomes an architectural bottleneck. In practice, every change to that module forces you to trace all the dependencies involved by hand: what's the impact? What else might break? This is why some areas of the system become no-go zones, releases have to be synchronised, and integration tests turn fragile as the points of contact keep multiplying.
At the highest level is architectural debt: the system's fundamental abstractions no longer reflect how the business actually works. The bounded contexts were designed when the company had a third of its current processes; the data structures model flows that no longer exist. This is the most expensive form of debt and the hardest to see, because it doesn't show up as an error in the code but as widespread resistance to any strategic change.
What isn't technical debt
Using the term loosely produces technical conversations that go nowhere. There are four categories that regularly get confused with debt, ordered by the type of constraint involved:
- Bugs: a bug is a behavioural error; the system does something wrong. Technical debt is a structural constraint; the system works, but it's hard to change. The distinction is conceptual, not just terminological: fixing each one calls for a different approach, different priorities and often different teams. A system with a lot of debt does produce more bugs, because hidden dependencies multiply side effects, but bugs and debt remain separate phenomena.
- A missing feature: not building a feature is a product decision, not an architectural problem. Technical debt is about the structure of the system that exists, not decisions about what to build.
- "Old" technology: the age of a technology is irrelevant. PostgreSQL on a server from 2012 isn't debt if the system is changeable and stable. Java in a critical service isn't debt if the team owns it and delivers at a healthy pace. Debt appears when that technology blocks evolution: it stops integration with new systems, demands skills that are increasingly hard to find, or introduces constraints that raise the cost of every change.
- Personal style: if a colleague names variables differently from everyone else, that's not debt. It's preference. It becomes debt only when it systematically breaks the coding standards the team has agreed on, because at that point it raises the whole team's cognitive load in a way that's measurable and real.
The principle that ties these distinctions together is already implicit in the opening test: debt is always relational. It doesn't exist in the code in the abstract, but in the relationship between the code and the change the system needs to support today. This is why the same module can be debt for one team and not for another, or debt at thirty people and not at six.
Fowler's four types
Martin Fowler described technical debt through a quadrant that crosses two axes: deliberate/inadvertent and reckless/prudent. The details are in his Technical Debt Quadrant; what matters here is the operational distinction the quadrant makes possible.
Deliberate, prudent debt is "let's do it this way for now, we know we'll need to rewrite it". That's a legitimate tool. You consciously choose a shortcut at a specific point in time, intending to pay it back once the context allows. Inadvertent, prudent debt is the opposite: you only discover afterwards that a decision made in good faith turned out to be wrong. It happens in every growing system; it's the debt of maturing knowledge, not of incompetence.
The distinction that matters in practice isn't between deliberate and inadvertent. The real problem is reckless debt: the kind that builds up unnoticed and keeps piling up until nobody can estimate the cost of a change any more. It's this form, not the others, that paralyses teams.
Why it's hard to see
Technical debt doesn't show up in reports, doesn't have a line on the balance sheet, doesn't trigger alerts. It shows up as background noise: estimates that blow out systematically, bugs that come back after being fixed, developers who say "it's best not to touch that part". By the time these signals are clearly readable, the debt has already been there for months.
There's an organisational mechanism that makes things worse. The people who rack up debt are often not the ones who pay for it: the team that takes the shortcut to hit the March deadline isn't the one doing maintenance two years later. This structural misalignment isn't a moral failing of organisations; it's a natural consequence of how responsibility and incentives get distributed over time. Organisations optimise for the short term because the costs of debt are deferred and hard to attribute.
The psychological mechanism is just as concrete. Debt builds up gradually, in increments that always seem manageable. Every single decision that creates it looks reasonable in the context where it's made: there's time pressure, the context isn't fully clear, the simple solution works for now. The problem isn't the individual decision, but the absence of a dedicated moment to measure the cumulative effect of every decision made under similar conditions. Organisations that manage debt well aren't the ones that avoid every shortcut, but the ones that stop periodically to ask themselves where they've ended up.
Once debt is already at work, the signals show up at three levels:
- In day-to-day work: estimates are systematically wrong and the gap grows sprint after sprint; CI/CD needs manual intervention; a new developer takes months to work independently. Nobody really knows the whole system: every area is someone's implicit property, and when that person isn't around, the work grinds to a halt.
- In delivery: releases become risky events, clustered on certain days and times; the roadmap becomes so unstable that the business has stopped asking for dates; adding developers doesn't speed things up. Every feature is slower than the last one: not because the team is less productive, but because every new element interacts with a wider, more fragile surface of code.
- In the numbers the business can see: the team fixes more than it builds, the bug rate climbs while features slow down; senior developers leave, citing frustration. Two signals a CEO or CFO can read directly: the cloud bill grows with no correlation to traffic, and the cost of each new feature exceeds the last one's with no product reason for it.
What it costs
The aggregate data all point the same way: according to McKinsey, CIOs at large companies (with revenue above a billion, in the finance and tech sectors) estimate that technical debt accounts for between 20 and 40% of the value of their technology estate, with a direct impact of 10–20% on the budget set aside for new products. Industry studies suggest that a significant share of developers' time goes on managing accumulated complexity rather than creating new value — a proportion that's hard to pin down precisely but that people on the ground consistently recognise. These are estimates based on large samples; in practice the gap varies a lot depending on the area of the system and the stage of growth.
The most useful number isn't the aggregate one but the operational one: how long does it take to make a small change in an area you know well? If the answer ought to be "a few hours" and it's actually "a week, because we need to work out how the effects propagate", that gap multiplies across every future change in that area. The cost of the next specific change is always more meaningful than any estimate of total debt. For the operational definition of cost of change and how to calculate it in practice, see our article on how to measure technical debt.
The reason the slowdown gets worse over time is that the relationship between complexity and speed is non-linear: beyond a certain threshold, every change demands an effort out of all proportion to its value. Adding complexity to an already complex system doesn't cost twice as much; it costs far more, because every new element interacts with everything that's already there. Adding developers to a system in that state doesn't help: it increases the coordination required before it increases output capacity at all. When the cost of adding a feature exceeds its business value, debt has stopped being a technical problem and become a strategic constraint.
What to do in practice
Three concrete actions for anyone who wants to stop guessing and start measuring.
- Measure cycle time and bug rate over the last 4–8 weeks: don't estimate, measure. Bug rate is the number of incidents or regressions per sprint. For the operational definition of cycle time and how it differs from the DORA Lead Time for Changes, see our article on how to measure technical debt. If you don't have this data, ask the team how long the last non-trivial change took in each critical area of the system. The numbers that come back are already a reliable proxy.
- Identify the area with the biggest gap between expected and actual effort: that's almost always where the bottleneck is. Not the biggest module, not the one with the oldest code: the one where estimates are most wrong, where pull requests stay open longest, where every release brings anxiety. In that area, debt is already charging a steep rent every week.
- Bring a concrete number to your next conversation with management: "this integration costs X weeks instead of Y because it cuts across these 3 coupled modules". The cost of the next specific change is always more convincing than any aggregate estimate of total debt. The Consistency Impact Calculator helps structure that measurement and turn it into an argument management can follow.
The QMates perspective
The starting point is to measure the effects: cycle time and bug rate. A cycle time that grows quarter on quarter without a matching rise in feature complexity is almost always debt at work. A bug rate climbing while development speed slows down shows the system is resisting change. When the two metrics get worse together, debt is no longer a hypothesis; it's an operational fact. Anyone who wants to start right away can use the Consistency Impact Calculator to structure the first measurement.
Metrics alone don't say where to intervene. The method we use is a progressive zoom: you start from the aggregate effect and work down to the specific bottleneck. In some organisations the blockage sits in the micro: functions with out-of-control cognitive complexity, untested modules nobody touches. In others it sits in the macro: boundaries between teams that no longer reflect business flows, architectures designed for an organisation that no longer exists. The level at which the bottleneck sits can't be predicted in advance; it depends on that specific organisation's history and on how architecture, team and business have stopped evolving together.
What we almost always find isn't bad code written by incompetent developers. It's architecture designed at a specific moment in the company's growth that no longer reflects how the business works today: module boundaries drawn when the team had six people and now has twenty, data structures modelling processes that have changed three times since, abstractions that were right at the time and now generate nothing but workarounds. The developers are competent; the system has stopped being consistent with the organisation that produces it.
That's why we don't come in with a list of refactorings. We first map where debt is blocking the flow of value, then we intervene there. Working on the wrong module, even if you do it well, doesn't move the needle on delivery speed. This is the principle behind our approach to strategic refactoring, and it connects to the Consistency Model: the question isn't "how much debt do we have?" but "is the system still consistent with the organisation that produces it?"
The wrong question is still the one we started with: how long has this code been here? The operational question, the one that actually matters, is different: how much will the next change you need to make cost? If you don't have an answer, you've already found the place to start.