Why Avoiding Technical Debt Might Be Your Biggest Mistake: Embrace Progress, Not Perfection
In this post, I’ll argue that technical debt isn’t inherently bad — it’s unmanaged technical debt that causes problems. Programmers who refuse to incur any technical debt pay a high price, using up one of a company’s most valuable resources: present time!
The Most Sensible Topic
Anyone who has programmed long enough knows the frustration of working with a tricky codebase. Often, as you wrestle with decisions made in the past, it’s easy to see how things could have been done better. “If only this function were set up a bit more generically! What if the output were clearer, or these libraries didn’t require such a rigid initialization order, or everything wasn’t so tightly coupled!” More often than not, the benefits of these past choices aren’t even clear. After all, what good comes from undocumented parts or poorly formatted code?
These shortcuts and design decisions from the past — the ones now creating challenges — fall under the term “technical debt”. Naturally, we want to avoid these headaches. So, what’s the logical approach? One tempting answer is to avoid incurring technical debt altogether by being extra careful from the start.
While this might seem like a sensible strategy, it can have serious financial consequences. As with most things, it’s not that simple.
Learn to Fear Debt from Germans and How to Use it From Americans
Technical debt takes its name from the concept of financial debt, and the two share many similarities. But is financial debt good or bad? In German, the word for debt is Schulden, which comes from Schuld, meaning “fault” or “blame.” Unsurprisingly, Germans are known for their caution around debt and for being financially conservative.
In contrast, Americans often refer to debt as “leverage.” Why leverage? Any business can take on a certain amount of debt. By taking in this small investment now, they can leverage it to generate greater future profits. From this perspective, debt sounds beneficial!
Consider a well-connected company with many potential markets. If it only relies on its own capital, it can afford to hire 10 people and work on one project. But with additional funding, it could hire 10 more people and tackle multiple projects, generating enough profit to pay off the debt and then some.
In this scenario, taking on debt benefits everyone. You get the capital to grow, new employees get jobs, your customers receive your product sooner, and the lender earns interest. In fact, when a company is under-leveraged — taking on no debt or holding too much cash — it may be missing opportunities to maximize its potential.
So, why are Germans so debt-averse?
Debt has two fundamental risks. First, it incurs interest, meaning it’s not enough to simply break even; you need a surplus in value generated to cover the added cost. Second, debt involves risk. Like any investment, it relies on an uncertain future.
Debt only serves its purpose when it’s used to do productive and meaningful things that generate value greater than the debt itself. If not, it turns into what’s called “unmanageable debt.” This is debt you can’t pay off with your regular income, where interest piles up, and eventually, the system collapses.
So, how does this all relate to technical debt?
Technical Debt: Same but Different
In many ways, technical debt works the same way. You leverage your codebase’s ability to take on technical debt to deliver features faster.
But like financial debt, technical debt also comes with interest. Major changes to a codebase are typically much faster and easier early in a project. If you don’t design your codebase to handle common use cases from the start, you’ll likely pay for it later — especially as the system grows and its complex interdependencies make changes harder.
When do you pay back technical debt and the “interest” on it? Every time you need to expand the system. Codebases burdened with technical debt are harder to develop and may eventually fall short of what you need them to do. And, much like financial debt, technical debt can lead to “bankruptcy.” Eventually, your team may struggle to meet its goals simply because the debt has become overwhelming.
So, why not just avoid technical debt altogether?
If you understand the benefits of financial debt, you’ll see why technical debt can also be valuable. There are faster ways to implement features, and there are more sustainable ways. Most codebases can handle a certain level of “quick fixes” and still meet your needs. Technical debt is about strategically leveraging that flexibility to deliver results sooner.
Take a look at the graph below:
This graph illustrates the balance between saving time now versus in the future. It shows four approaches:
- Refactoring (top left): Wastes present time but saves time in the future.
- Lucky You! (top right): The ideal case — saves time now and later.
- A Stupid Choice (bottom left): Wastes both present and future time.
- Technical Debt (bottom right): Saves time now but may waste time later.
In essence, choosing to take on technical debt allows immediate gains but may create challenges down the line, while refactoring sacrifices time now for a smoother future.
If an implementation choice saves you time now and in the future, it’s clearly the right choice! And if something wastes both present and future time, it’s best to avoid it. However, many programmers misjudge future needs and end up making “stupid choices” by prematurely refactoring.
The real consideration lies in the top-left and bottom-right quadrants. If there are quick, “hacky” solutions that help you reach your goal faster, it’s worth considering whether taking on that debt makes sense. But when is it reasonable to do so?
Sometimes, taking on technical debt makes sense in parts of a system you rarely touch. Customers care that the code works as expected, not about its architectural integrity. If you don’t need flexibility in certain areas, there’s no reason to over-engineer them.
Another key factor is the value of time. When weighing debt versus refactoring, ask yourself: how much time does it save now versus in the future? But remember, the value of time isn’t constant.
For startups or teams still searching for product-market fit, time is priceless — if they can’t deliver results now, there might not be a later. Conversely, once a team finds market fit and revenue or funding flows in, any technical debt, even if it costs 100 times as much to address, was still worth it. With more resources, that debt becomes manageable. And this is just one example of how the value of time can shift.
Sometimes, I think we programmers have a bit of a god complex. How else can you explain the confidence — and sometimes arrogance — we have in thinking we can predict future needs? We sit at our computers, mentally simulating how a system might be used down the road, get a dopamine rush from the idea, and then design “future-proof” systems. More often than not, though, this ends up being a waste of time, because we were wrong about what the product actually needs. The endless possibilities we planned for in our overly complex, generalized solutions turn out to be… useless.
So the real question isn’t why you shouldn’t take on technical debt. It’s that we often don’t know what the product needs, in either the near or distant future — and that is what matters most. Once you shift to this mindset, you’ll see that your top priority should be figuring out what’s worth building, as quickly as possible. Technical debt can help you get there. Once you’ve nailed down what’s truly needed, you can then refactor and “service your debt” to better support those newly defined use cases.
Empower the Debt Fearing German in You
None of this means you should let technical debt run unchecked. In fact, one of the CTO’s main responsibilities is to keep technical debt manageable. The goal is to ensure that your team can still develop what the product needs without being bogged down by debt.
As we discussed earlier, the aim isn’t to avoid debt entirely, but to monitor it and pay it off regularly. In practice, paying down technical debt means consistently refactoring parts of your tech stack. But how do you know which parts are ripe for refactoring?
Think back to the graph: refactoring saves future time at the expense of present time. Deciding when to refactor is almost like solving a mathematical equation. Here’s a formula that might help:
bool ShouldRefactor = (Time it saves in the future * value of time in the future + Value of possibilities the refactor enables) > (Time it wastes now *value of time now)
Let’s look at an example. Say you have a process that currently requires manual input. You set it up this way because it was faster to implement. You could automate it, making it faster and less error-prone. Should you do it?
To decide whether to refactor, ask yourself a few questions: How often do we perform this manual input? How long does it take? If someone makes a mistake, what’s the cost in time and money? Do you have past data on how often this type of mistake is made? How valuable is the time of the person doing this task, and how long would the refactor take?
Once you have these answers, think about “time to money.” For example, if refactoring takes two days but saves that time back within two months, it might be worth it — whether the savings come from reducing manual input or lowering the risk of human error.
The number of factors to consider can feel overwhelming. Opportunity costs mean you’ll need to prioritize different refactors against new features. And as I mentioned, things are constantly in flux —the value of time itself changes, how hard implementing certain tasks is changes not just due to technical or business reasons but also human reasons. Your employee’s focus, skills and available time changes. But as complex as it may be, what’s the alternative? Dogmatically follow best practices and vague intution for “good code” without making careful decisions?
I’ve seen many cases where people build elegant systems with a “time to money” value of 10–20 years! Or even worse, infinte time to money is also not rare. Way too often people put resources to develop for possiblities that never come to be needed! In those cases, no matter what you think about “good code”, “best practices” or “your expertise as an engineer”, just don’t do it. It’s a waste of resources. Always remember: you don’t know what the future holds, so validate as quickly and affordably as possible. Yes, unmanageable debt can threaten your company, and that’s why you should always keep an eye on it, but don’t let human biases or gut feelings lead you. Take a step back, open a spreadsheet, and do some quick math to figure out where your energy is best spent.
Hope you enjoyed reading this. You can follow me on various socials: https://ircss.github.io/