Cyclomatic complexity, coupling and cohesion, orthogonality, big O… are concepts that technical teams use when analyzing complex software systems and can establish why it is difficult to change them. Technical language refers to the technical aspects of those systems. But when we have to explain to non-technical people in the organization why an application has bugs, why a feature is not on time, or why we need a database expert to make a simple change to a service, we cannot use those concepts. To do that, we have to speak in a language that those non-technical people understand the intrinsic nature that exists in systems developed with software.
Ward Cunningham (among other things, author of the first wiki) found himself in this same dilemma when he had to explain to his boss the need to invest in refactoring the financial product they were working on. And he came up with the metaphor of “Technical debt”. Technical debt refers to the implied cost of additional work in the future, resulting from choosing an expedient solution over a more robust one. While Technical debt can accelerate development in the short term, it may increase future costs and complexity if is left unresolved/unpaid.
Cunningham presented the metaphor to the world in this short article [1] in 1992, (I guess at that time James Gosling was already working on the first version of Java, but it was still three years away from being released). So, 33 years later, we are still using the metaphor, because it is a very good one. It is an easy way to explain technical issues to the people who know about the business, using a common language that everyone can understand quickly.
Are we overusing the metaphor or using it as an excuse?
Before continuing, let me explain a personal story. Some years ago, working in a consultancy, we were hired for a project to improve an application and a team that had been stagnating for years due to a huge technical debt. After a quite thorough analysis of their systems, their technology, and their teams, we proposed to make an incremental migration of their monolith to smaller and more manageable services, breaking the coupling and allowing the systems to be divided into independent domains. Also accompanied by an up-skilling of their teams because we had also opted to change the technology they worked with, which was quite obsolete. Of course, we were going to explain that the reason why changes could not be made, why new features could not be added, why the performance was so poor, and why there was no way to test the application was… the technical debt they had accumulated over the last 10 years, and which had not grown exponentially in recent years.
When I showed the plan’s draft to the company’s CTO, he asked me not to mention the technical debt at all. The CEO was freaking out every time someone mentioned that there was technical debt in the code and that was why none of the plans were being met. He told me that the first time technical debt was explained to him, he paid attention and accepted the schedule slippage. The second time, he made a face and asked for more commitment. And the third time, he interrupted the person who was talking and said, “Stop, stop, stop, stop. Let me guess, it’s technical debt again, right? Don’t give me that bullshit excuse again.” From then on, it was not allowed to talk about technical debt in any meeting with this CEO.
In the last years some voices, usually from C-level, have expressed their discontent with the technical debt metaphor, even referring to it as a way technical teams have to avoid explaining their work or an excuse to ship faulty code. Also, in some cases when someone talks about technical debt, it sounds like a complaint and an excuse to use new technology that may not add anything to the business. Even Ward Cunningham reales this video [2] to explain misconceptions behind the metaphor.
In my experience, there are three main reasons for this dealignment between technical and non-technical people:
First and foremost, usually when we are discussing with top-management about technical debt, it is too late. Because the team, the product, and the company are already suffering the consequences. Those issues are shown in some, of all, of the following:
- The most obvious issue is that technical debt increases the cost of ongoing maintenance, making it more difficult to predict release schedules.
- Carrying technical debt into production raises the risk of outages, security breaches, financial losses, and potential legal issues due to violations of Service Level Agreements (SLAs).
- Future refactoring becomes riskier and more expensive, as modifications to production code carry a higher chance of causing disruptions.
- Productivity declines and delivery slows down. This adds stress on teams and can lead to reduced motivation and higher staff turnover.
- The cumulative effects of technical debt result in increasingly fragile systems, making significant improvements challenging. Inconsistent design and poor UI/UX can cause users to experience degraded performance and limited functionality.
We can agree that those consequences are really bad for team morale, company reputation, and users’ experience with the company’s products. So, it’s difficult to simplify all to a “debt” we should pay.
Second, the analogy between software and finance breaks down when it comes to how repayments work. When you borrow money, repayments are typically time-based, making it easy to track them (for example, €100 per month for 5 years at a 3% interest rate). If you don’t make your payments on time, there are clear consequences.
In contrast, repaying technical debt operates differently. There is rarely a set timeframe for repayment, and the cost of that debt can vary significantly depending on the context in which it was incurred.
And third, technical debt is linked to huge costs in refactoring or rewriting projects that can take years to finish, often providing little value to the company.
Those issues make C-level people get mad because executives expect to see diagrams with lines going up and to the right. With green numbers and positive deltas. Moreover, executives expect that if someone brings a problem, they also propose a solution, a plan, objectives, and the budget to carry it out.
When we are in this situation, we have to go beyond the metaphor, because technical debt is a simplification in non-technical language. It is not the whole picture, but a way to explain the result of some decisions made in the past and how that affects current and future work. However, technical debt is not the main problem; it is an effect of deeper issues that need to be addressed. But to go deeper we have to understand the causes.
Not all technical debt is the same
Martin Fowler [3] set an interesting point of view on the technical debt metaphor adding an extra ingredient, the internal quality. In his words, “Software systems are prone to build up of cruft (deficiencies in internal quality) that make it harder than it would ideally be to modify and extend the system further. Technical debt is the extra effort that it tasks to add new features, is the interest paid on the debt.”
And, you know what, internal quality does not matter to customers, nor investors and sometimes neither to top management. But customers, investors and managers do care that new features come quickly. Similar to financial debts, if the debt in your code is not addressed, it can accumulate “interest,” making it increasingly difficult to implement changes. Eventually, you may reach a point of “bankruptcy” where the code becomes so hard to maintain that you spend more time (and money) fixing issues than you do developing new features that add value.
We still do not have any causes here, but Fowler was the author of another brilliant reference in this field: the Technical Debt Quadrant [4], which explains the causes of different types of technical debt more explicitly.

Starting from the top/right:
- Prudent/Deliberate: perhaps this is the best example of technical debt as originally defined by Cunningham. It is typically used to accelerate time to market, often going unnoticed by the organization. High-performing teams usually operate this way, testing their hypotheses and refactoring as soon as possible. In the words of Kent Beck: “Make it work. Make it right. Make it fast.”
- Prudent/Inadvertent: it primarily occurs in a team that is growing and improving. This might be the most natural way of learning, but it requires us to take responsibility for the mistakes the team made and learn from them. Instead of blaming others, we should focus on understanding the lesson, adding it to the team’s knowledge base, and moving forward.
- Reckless/Deliberate: the main cause would be too much pressure to release something new. Teams instead of challenging business requests back, lower the bar on technical aspects. Eventually, they fall into a spiral because they do not have time to refactor and improve the code because the next new thing is in the backlog with a tight deadline already set.
- Reckless/Inadvertent: This is the worst-case scenario. The team lacks knowledge in both technical aspects and business topics, leading them to make questionable decisions. They are not learning from their mistakes and are simply adding more and more code in an attempt to fix the bugs.
How to pay technical debt properly?
We should assume some kind of technical debt, even in the best case: Prudent/Deliberate. And set up it as the standard of the internal quality we want to achieve in every team. But let’s be honest, there is no way to move from the bottom left (Reckless/Inadvertent) to the top right (Prudent/Deliberate), we have to transition from the other cases in the quadrant to achieve it. Also is crucial to take into account that technical debt is the result of socio-technical issues in the organization. You have to fix them first, or at least at the at the same time, you are paying the technical debt.

The easy one is to move from Prudent/Inadvertent to Prudent/Deliberate:
- Assume some technical debt is part of the journey.
- Protect the team from blaming by making mistakes.
- Encourage the team to challenge business requests that compromise technical solutions.
- Avoid solutions driven by individual efforts.
- Run away from over-engineering.
How to move from Reckless/Deliberate to Prudent/Deliberate:
- Reduce the WIP (Work In Progress).
- Set % of time dedicated to clean off/refactor.
- Educate stakeholders about the impact of technical debt on the product in the long term.
- Follow the “Boy Scout rule”: Leave the campground cleaner than you found it.
Moving from Reckless/Inadvertent to Prudent/Inadvertent:
- Invest in hiring and training.
- Set and ensure the teams are following good engineering practices.
- Set guidelines to refactor legacy code.
- Improve the feedback loops inside the team (pull request, Pair programming, Mob programming) and outside the team (reviews, audits, and feedback from other teams).
Discuss about internal quality instead
Now we have arguments to talk with the C-level. The issue we are trying to solve is not paying technical debt, but increasing the internal quality of the systems. The goal is to maintain the ability to easily and safely implement changes in a system that is continually evolving. This system should exist in a context that is nearly free of technical debt. And then, technical teams could provide new functionality in a shorter time and with fewer bugs.

Rather than continually repeating the phrase “technical debt” like an old, broken record, we should talk business language and refer to technical debt as operational risk: the risk of losses caused by faulty or failed processes, policies, systems, or events that disrupt business operations. We should focus on discussing:
- Systems most prone to critical failures and their potential costs (e.g., loss of sales, time spent fixing errors, loss of trust, low team morale, burnout).
- The impact of maintaining a system with low quality, performance, or maintainability.
- Actions to improve system resilience and prevent cost overruns.
All of the above will definitely cost money and require investment. So, be ready to do your numbers and present your plan based on strong arguments.
Extra-ball: Will AI help us to deal with technical debt?
In my opinion, it could help as a tool used to understand old legacy code, but if we use it for everything, it could be like adding more gasoline to the fire. As Kevlin Henney mentioned in his excellent talk: Technical neglect [5]: “Automating the creation of legacy code using LLMs, developers have redefined their job role as debugging and fixing such code.”
Even more, if junior developers rely too heavily on LLMs to complete tasks without learning and understanding the work (or the code), they will never develop expertise or critical thinking. They will never be able to improve the code because they do not have the necessary skills.
Use AI with caution!
References
[1] https://c2.com/doc/oopsla92.html
[2] https://www.youtube.com/watch?v=pqeJFYwnkjE
[3] https://martinfowler.com/bliki/TechnicalDebt.html
[4] https://martinfowler.com/bliki/TechnicalDebtQuadrant.html
[5] https://www.youtube.com/watch?v=MdWH9lbgEws
Feature image created by TungArt7