“We need to work on technical debt.”
If I had a kilobyte for every time I heard that sentence, I’d have a massive flash drive, probably blue. I like blue.
If you’re not a software person, you probably don’t know what technical debt is. If you are a technical person, it’s still likely you don’t actually know, but you think you do.
Let’s start by getting the record straight: technical debt is not “missing features”, “lacking security”, or “underwhelming performance”. That’s just gaps in the product.
Technical debt is similar to money debt: you get what you want now, and you pay for it later, with interest. The currency is time, effort, focus, and delivery speed. Having interest means that delivering new features takes more time, effort, and focus than it would have without the debt.
Most teams treat technical debt as a single, growing pile of “bad code” that needs fixing, if the gods of the roadmap allow. But just like financial debt, different types call for different strategies.
Here are the five most common types, in my experience, as if they were actual financial debt. This helps understand what the debt was for and how to manage it.
1. Mortgage Debt (Strategic Technical Debt)
A mortgage is a strategic debt: you borrow money at a cost because you need an asset right now.
In software, mortgage‑style technical debt happens when you deliberately take shortcuts to ship faster. The keyword is deliberate: you document the decision to take a temporary shortcut so you can go live sooner or deliver more when it matters most.
Examples are hardcoding values instead of building a full config system, copying and pasting code instead of extracting a proper library, and so on.
Mortgages are conscious business trade‑offs, and you should consider using them when they unlock real value. They are still debt, so you should pay them down when the compound interest starts to hurt. That’s when they block scale, slow delivery, or force hacks to implement basic changes.
2. Student Debt (Learning Tax)
Think of this as tuition fees for a degree that turned out to be less useful than you hoped. You made the best decision with the information you had, but if you could go back, you’d choose differently.
In software, this is when you pick a database that seemed perfect for 1,000 users but can’t handle 100,000. Or when you build your own authentication system because it looked simple, and now it’s a monster. Or when you write your whole thing on a promising framework that then becomes abandonware.
These were good decisions when you made them. You didn’t know what you didn’t know, that’s how learning works.
This is your learning tax, and it shows that your team is growing. The junior engineer who built that auth system five years ago is now the senior guy who knows exactly why you shouldn’t.
Pay this down when the pain of living with it exceeds the cost of changing it. And be kind to the young you: they did their best with what they knew.
3. Depreciation (Bit Rot / Entropy)
This is like owning a house that still works, but the roof tiles are no longer manufactured and the wiring standard changed three times since you built it. Nothing is broken, it’s just old.
Your code still works. Tests pass. Customers are happy. But you’re using ancient patterns, your dependencies are dozens of versions behind, and a core library has a bright yellow “DEPRECATED” badge on GitHub.
This is entropy. It’s inevitable. The ecosystem moves forward, and yesterday’s best practices become today’s legacy. Left to itself, the Universe will decay, nevermind your software application.
The tricky part is knowing when to care. If the deprecated pattern still works and you’re not adding features there, let it be. Those outdated dependencies? If they block security patches or features you actually need, it’s time to pay down.
Pay depreciation‑style debt when it blocks something important, not because Hacker News said your stack is old.
4. Credit Card Debt (Neglect and Habits)
Credit card debt works like this: you get a fancy lunch, a cool pair of shoes, that neat gadget, and suddenly you’re drowning.
Nobody decides to create 47 overlapping utility files. Nobody plans “completely inconsistent test fixtures across the codebase”. That giant if else tree with 23 branches started with 2.
This is death by a thousand small compromises. Each one seemed harmless: “I’ll clean this up later.” “This is just a quick fix.” “We’ll refactor next sprint.”
This is the most common type of technical debt, and also the easiest to get sorted a bit at a time.
The Boy Scout rule works well here: leave the code better than you found it. When you touch a brittle piece of code to add a method, fix that class. If you need to add a 24th branch to that if else chain, extract things out and sort it. If you need to change an old untested function, actually write the test for it.
Small, continuous improvements, no grand “cleanup sprint” required.
5. Environmental Debt (Platform and Ecosystem Risk)
This is like your insurance company going bankrupt. You did nothing wrong. The world changed around you, and now you’re holding the bag.
You built on Heroku’s free tier because it was perfect for your side project, then Heroku killed the free tier. You integrated with Twitter’s API. Then Elon showed up and it became a Cesspool. Your payment provider got acquired, and the new owner shut it down.
You built on reasonable assumptions that turned out wrong. This isn’t technical debt in the traditional sense: you didn’t take a shortcut, but the world under your feet shifted.
The frustrating part is that you often have no choice but to pay this down quickly. When an API shuts down, you migrate or you break. When a provider changes terms, you adapt or leave.
Pay this down when you’re forced to, or when the pain of workarounds exceeds the cost of migration. But don’t beat yourself up: this wasn’t your fault.
A Simple Technical Debt Decision Framework
Ok, so once you’ve identified the type of technical debt, you need to decide what to do about it. My unpopular opinion is that not all debt deserves attention, and some debt is better to live with than to fix.
I ask three questions.
1. What’s the compound interest rate?
How much is this technical debt slowing you down right now?
High‑interest debt screams daily, or at least weekly. Every feature requires workarounds. Every bug fix touches the same messy area. You spend more time fighting the debt than building. Your velocity is visibly lower because of this specific issue.
Low‑interest debt sits quietly. You rarely touch that code. When you do, it’s mildly annoying but not blocking. You just want to fix it because, in the immortal words of a senior engineer I worked with, “this code challenges my need for elegance”.
Be honest to yourself: don’t confuse “I don’t like looking at it” with “it actually costs us too much”.
2. What’s the cost to pay it down?
There’s a ton of ways to define development cost, but let’s keep it simple. Cost here is time it takes to fix it times the risk it introduces. Fixing code means changing code, and changing code means there’s a risk that code will break.
For example, a two‑day dependency upgrade with good test coverage and a clear path is low cost. A three‑month rewrite where you’ll discover new edge cases, introduce new bugs, and need to coordinate across teams is high cost.
3. What’s the opportunity cost?
There’s another element you need to throw into the mix.
If you spend three weeks refactoring your authentication system, what are you not building? The feature customers keep asking for? The bug fix annoying 30% of users? The performance improvement that would reduce churn?
Make it explicit. Put real alternatives on the table. Technical debt is a business decision, not a moral failing that must be atoned for. A simple prioritization framework helps make this comparison concrete.
Technical Debt Decision Matrix
| Interest rate | Cost | Action |
|---|---|---|
| High | Low | Pay down now (easy win) |
| High | High | Strategic project (get buy‑in, allocate time) |
| Low | Low | Boy Scout rule (fix when you’re nearby) |
| Low | High | Ignore it (seriously—live with it) |
When to Declare Bankruptcy (The Migration)
Every now and then, but more often than you might think, someone just gives up, throws their hands in the air and declares: “we should just rewrite the goddamn thing from scratch!”
If they have some authority, they could then get it their way. The full rewrite. Or, “the migration.”
In my career I have seen (counting off the top of my head):
- A migration to Java
- A migration to DynamoDB
- A migration to The Cloud
- A migration to PostgreSQL
- A migration to Microservices
None, ever, not one, went as planned, and met cost requirements and time constraints. Zero, nada, nil.
Now, I’m not saying you should never rewrite a system, or migrate it.
But consider a rewrite only when all four conditions are met:
Crippling interest rate: Simple changes take weeks; customers are leaving or blocked.
Incremental improvement failed: You’ve allocated serious time to debt, and it still keeps growing.
Executive support: The organization accepts slower feature delivery for a while.
Clear migration path: You have a realistic plan for running systems in parallel, migrating customers, and rolling back if needed.
And be aware this is going to be painful, expensive, and frustrating.
Bankruptcy is nuclear. Incremental improvement usually wins.
Using This Framework in Real Conversations
Next time someone says “we need to work on technical debt”, don’t stop there. Make it specific.
Categorize: What type is it? Mortgage, student, depreciation, credit card, or environmental.
Measure: What’s the interest rate? What’s the cost to pay down?
Decide: Use the decision matrix.
Commit: Once you decide what to do, stick to it.
So the next time someone says “we need to work on technical debt”, don’t reach for a rewrite and don’t schedule a vague “cleanup sprint”.
Figure out what kind of debt it is, walk through the three questions, and decide whether to pay it down, chip away at it, or live with it on purpose.
And if you do it, let me know how it goes!