Skip to main content
Back to Blog
Technical Debt Isn't the Problem. Unpaid Technical Debt Is.
Development

Technical Debt Isn't the Problem. Unpaid Technical Debt Is.

Daniel Wohlfarth
Daniel WohlfarthAug 13, 20267 min read
#software-engineering
#technical-debt
#software-architecture
#engineering-culture
#code-quality
#developer-experience
Not all ugly code is technical debt. Sometimes it's a conscious trade-off. The real problem starts when nobody remembers the trade - and the interest keeps growing.

Every engineer learns the phrase early: technical debt. And like most things we talk about, we've turned it into something that sounds inherently bad. Pay it down. Clean up the code. Refactor before it becomes a problem.

But I think we got something wrong. Technical debt isn't the problem. Unpaid technical debt is. And sometimes what we call technical debt isn't debt at all.

Not everything ugly is debt

Say you're building something new. You don't know if anyone will use it, which features matter, or whether the architecture you're sketching today survives six months of real traffic. So you make a call: build the simplest thing that works. The config is hardcoded. The API isn't beautifully abstracted. There's one application instead of five services. You duplicate a bit of code instead of building a framework for it, because there are three events in the whole system, not thirty.

An engineer looking at that code six months later might call it technical debt. Maybe. But if the decision was "we don't know enough yet to justify the more complicated solution," that's not debt. That's just engineering.

It's a deliberate trade

The original metaphor still holds up if you take it seriously. Debt isn't inherently bad. You borrow money because you want something now instead of waiting until you can afford it outright, and technical debt works the same way. You accept a less-than-perfect implementation because it gets you something valuable now: a validated idea, a shipped feature, a met deadline, a tested assumption.

You knowingly accept that the solution might need to change later. That's not bad engineering, and refusing to take on any debt at all can be worse, because the alternative is sometimes three months spent building the perfect system for a problem that turns out not to exist.

The real question is what it costs later

"Is this technical debt?" is the wrong question. The better one is: what will this decision cost us later? That's where the debt metaphor actually earns its keep, because debt has interest, and so does this.

A shortcut that saves you four hours today is fine, unless it turns every future change into a two-day job. Then you're paying interest.

Take two decisions. First, you hardcode a config value because there's currently no reason for it to be configurable. Six months later there's still just the one value, nobody cares, and the shortcut cost nothing. Second, you build a shared abstraction because three features currently need something similar. Six months later seven teams depend on it, every change needs coordination, and nobody remembers why it exists in the first place. That one hour of work is still accruing interest.

That second case is technical debt. The first one just isn't.

Some debt has zero interest

This is where teams get confused. We see code that isn't ideal and immediately label it debt, but not every imperfect implementation creates future cost. Sometimes code is just fine. Not the abstraction you'd design today, not the naming you'd choose, but it works, it's understandable, it isn't causing incidents, and it isn't slowing anything down.

Changing it anyway is often just theatre: spending time to make code look more like the code you personally would have written. Experience gives you the ability to recognize better designs, but it can also make you overconfident that your preferred design is necessary. Those aren't the same thing, and it's worth staying honest about which one you're actually reacting to.

The debt nobody remembers is the dangerous kind

There's a worse category: forgotten debt. Someone takes a shortcut, knows it's temporary, leaves a comment, maybe even files a ticket. Then the project grows, people leave, new people join, the product changes, and the original context disappears.

Years later someone finds:

// TODO: Replace this once we migrate to the new system.

Nobody knows what "the new system" was. Nobody knows why this exists or whether it's still needed. This is where technical debt actually becomes dangerous, not because the code is ugly, but because the organization has lost the reasoning behind the decision.

Code remembers what, rarely why

This is one of the biggest problems in long-lived codebases. The code tells you what the system does, not necessarily why. You can read the implementation, follow a request through five services, understand the schema and the control flow, and still have no idea why it was built this way.

Maybe the simpler approach was tried and failed. Maybe a third-party API used to behave differently. Maybe the requirement came from a customer who's long gone. Maybe what looks like a terrible architecture is actually a very good decision made under constraints that no longer exist.

Without that context, engineers tend to do one of two things: preserve the code forever because nobody wants to touch it, or rewrite it because nobody understands it. Neither is great.

Technical debt needs a receipt

If you're deliberately taking on debt, write down why. Not a 40-page architecture doc, just enough to answer "why did we choose this":

We're keeping this implementation simple because we don't yet know whether users will need multiple payment providers. If that changes, extract the provider interface. Don't generalize this before then.

That's worth a lot six months later, because it tells the next engineer this wasn't forgotten, it was intentional, and they can make a fresh call based on current reality instead of guessing at old ones.

Sometimes the right ticket is "do nothing"

Suppose your team has 40 open debt tickets. Before spending a quarter fixing them, ask: is this actually slowing us down, causing bugs, raising operational risk, blocking a change we need to make, or making the system harder to understand? Is the original constraint even still true?

If not, it might not be worth paying back yet. Every hour spent paying down debt is an hour not spent building something else. Engineering has opportunity cost too.

Refactoring isn't free either

There's an uncomfortable truth here: paying down debt has its own cost. A big refactor can introduce bugs, delay features, create migration headaches, or just burn weeks making the architecture prettier without changing anything a customer notices. Sometimes you're just swapping one kind of complexity for another.

Teams that get obsessed with eliminating technical debt can end up creating a new kind: unfinished work. A beautifully refactored subsystem sitting next to three features that never shipped isn't obviously a better outcome.

The real skill is knowing which debt matters

A junior engineer looks at a codebase and sees "this is ugly." A more experienced one sees "this is ugly, but harmless," and knows that some perfectly clean-looking code is actually terrifying, because it's on the critical path, everything depends on it, nobody understands how it works, and a failure there takes down production.

The skill isn't spotting bad code. It's spotting expensive bad code. That distinction is what separates the two.

Not all debt should be paid

Some debt needs to be paid immediately. Some should be scheduled, some monitored, some just left alone. Knowing which is which is the actual job.

The goal of a healthy engineering org isn't "we have zero technical debt." That's not realistic, and it's probably not even desirable. The real goal is knowing where your debt is, why you took it on, and what it's currently costing you.

The interest is what matters

Technical debt isn't dangerous because the code isn't perfect. It's dangerous when yesterday's shortcut starts taxing every decision you make today. A five-minute workaround that stays a five-minute workaround was never a problem. One that turns every future change into a two-day investigation is.

So next time someone says "we need to pay down our technical debt," maybe ask a different question. Not "what's wrong with the code," but what is it actually costing us. Imperfect code isn't necessarily debt, and debt isn't necessarily bad. The real problem is debt that keeps collecting interest while everyone forgets why they borrowed it in the first place.