Most Technical Debt Isn't Technical Debt

The Difference Between Debt and Preference

I posted something on LinkedIn this week that started a fight.

"Technical debt. Most of the time, you mean it's not your code."

People agreed. People pushed back. A few got genuinely angry. And the thread revealed something I've been thinking about for years but hadn't fully articulated.

The term "technical debt" has been inflated beyond recognition. It used to mean something precise. Now it means whatever an engineer wants it to mean when they want permission to rewrite something.

The Original Definition Was Precise

Ward Cunningham coined the term in 1992. He was describing a specific tradeoff ... shipping code you know is imperfect because the business needs speed now, with the understanding that you'll pay down the shortcut later. A conscious decision with a known future cost. Like a loan. You take it on deliberately. You know the interest rate. You plan to pay it back.

That definition is useful. It maps cleanly to business language. Leadership can understand a deliberate tradeoff between speed now and cost later. They make those decisions every day.

But that's not how most engineering teams use the term.

How Teams Actually Use It

In practice, "technical debt" has become a catch-all for anything an engineer would have done differently. The function is 50 lines and they would have split it into three. The previous team used classes instead of hooks. The naming conventions feel inconsistent. The architecture doesn't match what they learned in their last job.

None of that is debt. That's preference.

Debt has interest. It compounds. It creates drag that gets worse over time. Preference just sits there, quietly offending someone's sense of elegance while the system ships perfectly fine.

I've watched teams spend entire quarters rewriting systems that were working. Delivery was on track. Defect rates were stable. The business felt no pain. But the engineering team called it debt because the patterns bothered them, and leadership approved the work because nobody wants to be the person who ignores technical debt.

That's how preference gets laundered into priority. Call it debt. Attach urgency. Get it on the roadmap. Replace someone else's judgment with your own and call it an investment.

The Interest Test

Here's how I decide if something is actually debt.

Is delivery slowing down because of this? Are features taking longer to ship than they should? Are changes that used to take a day now taking a week because of coupling, complexity, or fragility? If yes, that's interest accruing. If no, the code might be ugly but the interest rate is zero.

Is it causing defects? Are bugs showing up in the same area repeatedly? Are deployments breaking because of this specific part of the system? Are engineers afraid to touch it because changes have unpredictable consequences? That's interest. The system is charging you for the shortcut whether you notice or not.

Is the business feeling pain? Are customers affected? Are incidents happening? Is operational cost increasing? Is onboarding slower because new engineers can't understand the system?

If the answer to all three is no, stop calling it debt. You're looking at code that works differently than you'd have written it. That's not the same thing.

Mohammed Ibrahim said it well in the comments ... "Strong teams measure debt by delivery friction, not code elegance. If the system still changes safely and quickly, the interest rate is probably zero."

Debt shows up in delivery friction long before it shows up in architecture diagrams. When lead time stretches, changes feel risky, and teams start routing around parts of the system ... that's the interest payment. If none of that is happening, the interest rate really might be close to zero.

The mistake a lot of teams make is trying to pay off debt the business was never charged for.

The Ego Problem

One of the sharpest observations from the thread came from Bob Salmon. He asked how much of what gets called technical debt is really "I want something good on my CV" or "this is the architectural flavor of the week and I haven't bothered to evaluate it for our context."

That question should make every engineering leader uncomfortable. Because the answer, honestly, is a lot.

Engineers are craftspeople. We care about how things are built. We have opinions about patterns, conventions, naming, structure. Those opinions are often well-informed. They come from experience. They come from having seen what works and what doesn't.

But they're still opinions. And when an engineer labels their opinion as debt, they're borrowing the urgency of a business problem to fund a personal preference.

I've done this myself. Early in my career, I wanted to rewrite everything. Newer framework. Better patterns. Cleaner architecture. At Life is Good, I was ready to approve a $500K framework migration in my first quarter because the vendor had a slick presentation and the architecture looked modern.

I asked one question ... "Can we run the numbers?"

The numbers said no. The migration was technically possible and financially stupid. That $500K went toward features that actually moved conversion. The framework migration still hasn't happened. And doesn't need to.

Not every instinct to improve the system is ego. But enough of them are that it's worth asking the question before approving the work.

The Premature Abstraction Trap

The thread went somewhere I didn't expect. Andres Vaquero and Paul Jardine got into a deep conversation about frontend architecture, service boundaries, and when to generalize.

Paul made an observation that I've seen play out dozens of times ... when people build something new instead of reusing a general solution, that's real debt. They were too lazy or couldn't grasp the concept, so they built something else. Code becomes spaghetti. You have 100 ways to do the same thing and a change affecting everything costs 10 times as much.

He's right. And he's also describing the opposite problem.

Teams also create debt by generalizing too early. They see two similar patterns and immediately build a shared abstraction. "We should have a reusable component for this." "Let's build a framework so we never solve this problem again."

The abstraction gets built before the pattern is stable. Now you have a framework nobody fully understands, and every edge case bends it a little more. The team spends more time working around the abstraction than they would have spent with simple duplication.

Sometimes duplication is cheaper until the system proves the pattern is real.

The best teams I've worked with wait for the third instance before abstracting. First time, just build it. Second time, notice the pattern. Third time, now you have enough data to know if the abstraction is worth the complexity. Two instances isn't a pattern. It's a coincidence.

Once logic leaks into the wrong layer ... business rules in the component tree, UI concerns in the service layer ... it becomes incredibly hard to untangle later. Every UI change starts dragging business rules with it. Teams slowly accumulate the same logic in five different components because the boundary wasn't clear early on.

That's usually when velocity really starts to degrade. And that's real debt, because the interest compounds with every change.

What Strong Teams Do Differently

Strong teams fix what harms speed, quality, or scale. They leave alone what merely offends elegance.

That sounds simple. It requires enormous discipline. Because every engineer on the team has an opinion about how the code should look. And those opinions feel urgent. They feel important. They feel like they matter.

They do matter ... to the engineer. They don't always matter to the business.

The framework I use before approving any refactoring work:

How much time does this cost us per sprint? If adding features takes three days instead of one because of this code, that's measurable. "It's messy" isn't.

How many incidents does this cause? If it breaks every other deploy, that's quantifiable. "It's not clean" isn't.

What's blocked because of this? If the Q4 roadmap depends on fixing this, that's a cost. "I don't like the architecture" isn't.

If the team can't quantify the pain, I don't approve the fix. Not because I don't trust them. Because if we can't measure the problem, we can't measure whether we solved it.

Measure the cost. Fix the things with real interest. Leave the rest alone.

The Conversation Nobody Wants to Have

The uncomfortable truth underneath all of this is that a lot of engineering roadmaps are just preference cleanup wearing finance language.

"We need to pay down technical debt this quarter" sounds responsible. It sounds like the team is being disciplined. It sounds like an investment in the future.

Sometimes it is. Sometimes the system genuinely needs attention. The interest is compounding. Delivery is degrading. The business is feeling pain.

And sometimes it's an engineer who joined six months ago, looked at code written by someone who left two years ago, and decided the whole thing needs to be rebuilt. Not because the system is failing. Because it's not how they would have done it.

Those two things look the same in a sprint planning meeting. They sound the same in a roadmap review. The only way to tell them apart is to measure.

Does this slow us down? Does this cost us money? Does this create risk?

If the answer is yes, fix it. That's debt.

If the answer is no, leave it. That's taste.

And taste is a fine thing to have. It just shouldn't be funded like an emergency.