A field needs to be added, a rule needs to change, or a customer bug needs a quick fix, and on paper the work looks small enough to finish in a day. Then someone points out that the old service is involved, the tests are unreliable, and the only person who still remembers why the workflow behaves that way is on holiday, which is usually when technical debt becomes visible.
Technical debt occurs when we solve a software problem with our limited understanding of the business at the time. We start building a solution to get feedback as early as possible. Instead of spending additional time on business requirements, we prefer to deliver software early. Therefore, we borrow some time from the future.
By the time we deliver the software, we might have accumulated some burden. As the software grows, our limited understanding, as well as additional factors, become hurdles. We might be okay with those limitations for a while. We can pay interest, where interest is the extra time required to make a change. We then address the debt to cut down the interest we pay. As we deliver new features, we accumulate new debt. The debt will never be zero. Therefore, we should keep the interest payments affordable.
If the debt becomes a huge pile, we might go into arrears. We would then lose the capability to deliver new changes. Typically, arrears happen when everyone is firefighting incidents and bugs instead of building.
Although technical debt does not necessarily suggest poor quality software, the industry has often used it as a metaphor for sloppiness. Thinking of technical debt as an excuse for subpar software is counterproductive. There are three fundamental reasons for technical debt: deliberate decisions, outdated design, and rotten components.
Addressing technical debt
Technical Debt Is Not Always Bad Engineering
Technical debt gets used as a nicer word for mess. No tests. Poor design. A shortcut that should have never left someone’s laptop. That kind of work exists, and calling it debt can make it sound more respectable than it deserves.
But real technical debt is not always careless. Sometimes we make a deliberate trade-off because the business needs feedback. Sometimes the design was reasonable when the system was smaller. Sometimes a component rots because everything around it changed. If every ugly part of the system gets called technical debt, the term stops being useful. You can no longer separate a reasonable trade-off from poor engineering discipline. Worse, teams start using the debt metaphor as cover for work that was simply not done well.
Where Technical Debt Comes From
Once we separate technical debt from plain bad engineering, the next question is where it actually comes from. I usually see three sources: deliberate decisions, outdated design, and rotten components.
Deliberate Decisions
One might argue that skipping unit tests is a deliberate decision. Hence, it is part of technical debt. I do not think that framing is useful. There is no justification for such a move when the team immediately pays a harsh penalty. That is not debt. That is just removing a safety net and hoping nobody falls.
The deliberate decisions that qualify as technical debt are usually around business use cases. We might lack clarity about the needs and requirements of a system. Part of the initial development is then about delivering software early, giving people something concrete to react to, and uncovering unknowns.
The developed software might not decently meet expectations. It might still be good enough for the time being. We incur debt. Many more decisions later, we accumulate much more of it.
Outdated Design
The second way to incur technical debt is through design that no longer fits. There is no way to get everything right in the first place. We do our best at the time with the information we have. We make some trade-offs. Nonetheless, those trade-offs might not work out well.
Every time we want to add a new feature, the design can cause friction. Refactoring accidentally poorly designed software might be challenging as well. Similarly, our architecture might not hold itself well against time. What helped us move faster earlier can later cause a loss of productivity. The design may have been reasonable once. The problem is that the system kept changing around it.
Rotten Components
Last but not least, rotten components and services contribute to technical debt. A software component might get more features and gradually gain more responsibility. The complexity comes naturally due to incremental changes to the system. Nevertheless, it can get to a point where the boundaries of the service become unclear.
Adding new features or clearing out bugs becomes time-consuming. People start avoiding the component. Reviews become slower because changes are harder to reason about. Testing becomes defensive because nobody fully trusts the behaviour anymore. Hence, it becomes a debt that we incur as long as we keep it running as it is.
Indicators
We pay interest on accumulated debt through slower delivery, fragile changes, and extra coordination. Before that interest starts blocking new features, we need to reduce the debt.
The hard part is deciding where to start. There is no clean recipe for this. A team can still look for signals that show where the interest is becoming expensive.
- An old tech stack can slow down development. Aged libraries, unsupported frameworks, and outdated dependencies make ordinary changes harder than they need to be.
- Complexity analysis can point to areas that might benefit from refactoring. These tools will not make the decision for us, but they can show where the system has become harder to reason about.
- Frequent incidents around a particular service or component show that the debt is no longer theoretical.
- Many bugs reported against the same component suggest that it needs servicing.
- Refusal from the team to work with a particular component is also a signal. When engineers keep avoiding the same area, the system is telling us something.
Even then, not every painful component deserves immediate attention. We do not have infinite budget or time. Some debt is ugly but cheap. Some debt is annoying but rarely touched. The parts worth addressing are usually the ones that sit on the path of frequent change, customer impact, or operational risk.
Paying Down the Debt
Once we get a sense of what we want to refactor, we need a methodology. I prefer continuously addressing technical debt instead of waiting for stop-the-world events. Teams can create technical debt issues for refactorings, improvements, migrations, test coverage, or dependency upgrades. We then classify these as engineering health work.
Engineering health should be 20-25% of overall engineering work. That gives the team a steady budget to keep improving the software stack without turning every cleanup effort into a special campaign. This matters because technical debt rarely becomes painful all at once. It usually gets worse in small increments. A component becomes harder to test. A dependency gets older. A service takes longer to change. The team works around it for a while. Then one day, the workaround becomes the normal way of working.
Although ongoing treatment is preferable, some teams inherit systems with significant problems. In those circumstances, we might need to stop the world and bring the debt back to a manageable amount. Examples include bugs and incidents that the team can no longer address promptly. At that point, we are no longer just paying interest. We are in arrears.
Partner Collaboration
Engineering health work might not seem relevant to product development. Several partners might not understand why we need it, especially when the alternative is visible feature work. The key is to communicate the cost early and constantly. We should point out the likely outcomes of ignoring technical debt: slower delivery, more defects, riskier releases, and more operational noise. The other key is to prevent stop-the-world events. Teams should deliver without disruption as much as possible.
Making technical debt tasks part of routine engineering work is easier than asking for a full stoppage later. If we have to go into stop-the-world mode, then we need to incorporate it into the company’s yearly and quarterly roadmap. Otherwise, it will look like engineering suddenly disappeared into a cave.
Nowadays, most stakeholders understand the concept of technical debt. The hard part is still choosing engineering health work over feature work. Product work usually has a clearer justification. A feature can come with a customer request, a revenue number, or a market deadline. Engineering health work often has to argue from avoided pain.
The angle we should take is velocity and stability. If we do not pay down the debt, we keep paying more interest. Delivery slows down. Releases become riskier. Customers experience more bugs and downtime. At some point, the business pays for the debt anyway, just in a more expensive and less planned way.
Not All Debt Deserves Payment
Not every piece of technical debt deserves immediate attention. Some code is ugly but stable. Some components are painful but rarely touched. Some old services are annoying, but they sit outside the path of active product development. Refactoring those areas might feel good, but it may not change much for the team or the business.
The debt worth paying down is usually the debt sitting on the path of frequent change, customer impact, or operational risk. If a component slows down every new feature, causes repeated incidents, or makes releases risky, we are paying interest often. That is where the work becomes easier to justify.
This is also why technical debt conversations should not be framed around cleanliness alone. Clean code is good, but the stronger argument is cost. How often do we touch this area? How much time do we lose? How many incidents come from it? How much confidence does the team lose when changing it?
Conclusion
Software development requires steady investment and maintenance. Technical debt is part of that work. The goal is not to remove every debt from the system. That is not realistic. The goal is to identify the components and services where the interest is becoming expensive, choose the activities that need immediate attention, and address them as part of the normal engineering routine.
This only works when the work is visible. Technical debt should not stay as a private engineering complaint. We need to communicate the cost to stakeholders, explain the trade-offs, and show how engineering health protects delivery, velocity, and stability. That is how teams keep delivering high-quality software without letting the system rot quietly in the background.
References
Article: The WyCash Portfolio Management System
Article: Managing Technical Debt
Article: Erasing tech debt: A leader's guide to getting in the black
Article: 3 Main Types of Technical Debt and How to Manage Them
