We were moving fast.
Features shipped every week.
Stakeholders were happy.
The backlog was finally under control.
Then, almost without noticing, everything slowed down. A feature that should have taken a day took three abd a small change broke something unrelated. Fixing bugs started taking longer than building new features.
And at some point, someone said:
“But this used to be faster, right?”
They weren’t wrong. At some point in the past, things were faster, but that speed came at a cost.
A cost that wasn’t visible at the time.
A cost that quietly accumulated.
This is something I’ve often wanted to explain to non-technical stakeholders:
we didn’t suddenly become slower, we’re just paying back what we borrowed.
TL;DR
- Technical debt behaves like a high-interest loan: it feels cheap at first, but becomes expensive over time.
- The real problem isn’t having technical debt, it’s letting it compound unmanaged.
- Refactoring isn’t a cost, it’s an investment with measurable ROI in team velocity.
Table of Contents
- The Illusion of Speed
- Technical Debt as a Financial Model
- Where the Interest Shows Up
- The Compounding Effect
- When Teams Hit Default
- The ROI of Refactoring
- When NOT to Refactor
- Practical Ways to Manage Technical Debt
- Final Thoughts
The Illusion of Speed
Technical debt often starts as a conscious trade-off:
- You skip a refactor.
- You duplicate a bit of logic.
- You hardcode something “just for now”.
And in the moment, it feels like the right decision because:
- You move faster.
- You deliver sooner.
- You hit the deadline.
That’s why it’s so hard to avoid, because it works, but what you’re really doing is borrowing time from your future self and like any loan, that time comes back.
Technical Debt as a Financial Model
One of the most useful ways to think about technical debt is to treat it like an actual financial system.
Not just a metaphor, but a model.
Principal: the shortcut
The principal is the initial shortcut you take:
- skipping a proper abstraction
- duplicating logic instead of extracting it
- shipping a workaround instead of fixing the root cause
Individually, these decisions are often reasonable.
Sometimes even necessary.
Interest: the friction
The interest is what you pay every time you touch that code again. It shows up as:
- extra time to understand what’s happening
- unexpected side effects
- more effort to implement even simple changes
You don’t notice it immediately.
But it’s there, every time.
Compounding: the multiplier
And then comes the real problem: compounding.
Each new feature built on top of messy code increases the cost of the next one.
Not linearly.
Exponentially.
Where the Interest Shows Up
Interest doesn’t arrive as a big, visible cost, it shows up as friction.
Small things that make your work just a bit slower, every single day.
- A feature takes longer than expected
- A bug fix introduces another bug
- You spend more time reading code than writing it
- Onboarding a new developer becomes difficult
Or even something like this:
// "temporary" workaround from 6 months ago
if (user.role === "admin" && featureFlagX) {
// special case inside special case
}
Nothing here breaks the system, but everything here slows you down.
That’s the interest you’re paying.
The Compounding Effect
This is where things become dangerous.
Technical debt doesn’t just add cost, it multiplies it. Every new feature built on top of unclear or fragile code becomes:
- harder to implement
- harder to test
- harder to change
So the next feature takes longer.
And the next one.
And the next one.
At some point, the system isn’t just complex.
It’s actively resisting change.
And that’s when velocity starts dropping, even if your team hasn’t changed at all.
When Teams Hit Default
In finance, default happens when you can’t repay your debt anymore.
In software, it looks different, but the signal is clear.
You see it when:
- refactoring is always postponed
- certain parts of the codebase are avoided
- every release feels risky
- progress slows down despite increasing effort
At this stage, teams often react the wrong way.
They try to push harder.
More hours.
More pressure.
More “just ship it”.
But the problem isn’t effort.
It’s accumulated complexity.
And no amount of speed can compensate for that.
The ROI of Refactoring
Refactoring is often perceived as a cost, something that slows down delivery, something you “don’t have time for”, but that perspective ignores the return.
Refactoring is an investment.
And like any investment, it pays off over time.
Let’s make it concrete, imagine:
- A feature currently takes 3 days to implement
- After refactoring, similar features take 1.5 days
That’s a 50% improvement.
Over 10 features, you’ve saved 15 days.
That’s not just cleaner code, that’s recovered velocity.
And just like debt compounds negatively, good code compounds positively:
- faster development
- fewer bugs
- easier onboarding
- more confidence in changes
This is where engineering meets business, because velocity is not just a technical metric, it’s a business advantage.
When NOT to Refactor
Not all debt needs to be repaid immediately, and not all code needs to be perfect.
Refactoring everything blindly can be just as harmful.
Avoid refactoring when:
- the code is rarely touched
- the feature is about to be replaced
- you’re still validating a product idea
- the cost clearly outweighs the benefit
The goal is not to eliminate technical debt, but it’s to manage it intentionally.
Practical Ways to Manage Technical Debt
You don’t need a full rewrite to stay in control, you need consistency.
A few habits make a huge difference over time:
- Refactor as you go: improve code while you’re already working on it
- Make debt visible: track it instead of hiding it
- Set a refactoring budget: even 10–20% of time is enough
- Review for maintainability, not just correctness
- Call out complexity early, before it spreads
These are small actions, but they prevent large problems.
Final Thoughts
Technical debt isn’t the enemy, it’s a tool.
Sometimes you take on debt to move faster and that’s a valid decision, but if you ignore it, it becomes a liability and, eventually, it starts slowing everything down.
The real problem isn’t having technical debt.
It’s pretending you don’t.
If this resonated with you:
- Leave a ❤️ reaction
- Drop a 🦄 unicorn
- Share the most “expensive” piece of bad code you’ve seen
And if you enjoy this kind of content, follow me here on DEV for more.
Top comments (4)
I read this and nodded… then winced a bit.
Yes, bad code is like a high-interest loan — but the uncomfortable truth is: most teams don’t realize they’re borrowing. Nobody wakes up thinking “today I’ll write something unmaintainable” — it’s usually deadlines, context switching, and just trying to ship. ()
Where I slightly disagree is this: not all “debt” is the same. Some debt is intentional — you ship fast, learn, and repay it quickly. That’s leverage. The real killer is the silent kind: the hacks that become architecture, the TODOs that become policy, the “we’ll fix it later” that nobody owns. That’s when the interest compounds and starts eating velocity sprint after sprint. ()
I’ve seen teams blame velocity drops on process, meetings, or even people — when the real culprit was a codebase nobody wanted to touch. When adding a small feature takes 3x longer than it should, you’re not slow — you’re paying interest. ()
The takeaway for me:
Debt isn’t the problem — unmanaged debt is
Speed isn’t the enemy — unpaid shortcuts are
And refactoring isn’t “nice to have” — it’s how you stop the bleeding
Good article. Just missing one harsh reality:
you don’t notice technical debt when you take it — you notice it when your best engineers start avoiding parts of your system.
Thank you @paolozero , I really appreciate your comment, especially the distinction between intentional and silent debt. That’s a nuance I didn’t fully unpack, and you’re right: not all debt is created equal.
I like how you framed “leverage vs. liability.” I’ve seen that play out too: teams making conscious tradeoffs to learn fast, then actually circling back to clean things up. When that loop exists, debt can be a tool. When it doesn’t… it becomes exactly the kind of drag I was warning about.
Your point about engineers avoiding parts of the system hits hard. That’s usually the moment when debt stops being an abstract concept and becomes a cultural problem. Once people start routing around the code instead of improving it, velocity loss is just the visible symptom.
If I were to extend my own argument after your comment, I’d say: the real danger isn’t just the “interest rate”, it’s losing the team’s willingness to engage with the codebase at all.
Thanks for adding that layer, this is exactly the kind of discussion I was hoping the article would spark.
I really like the framing of bad code as a high-interest loan, it’s one of the clearest ways to explain technical debt to non-engineers.
What stood out to me is how subtle the “interest payments” are. It’s rarely a dramatic failure, more like a constant tax on everything: slower feature delivery, harder debugging, more regressions. As you mentioned, it quietly eats away at team velocity until it becomes the norm.
One thing I’ve seen work well in practice is making that interest visible. Instead of saying “this code is messy,” framing it like:
“This shortcut is adding ~20% extra effort to every change in this area”
suddenly turns a technical concern into a business decision.
Also, I appreciate the implicit point that not all debt is bad, it’s the unmanaged, high-interest kind that kills teams. Strategic shortcuts with a repayment plan can be valuable, but most teams underestimate how fast “we’ll fix it later” turns into never.
Curious: have you found any effective ways to quantify or surface this “interest” to stakeholders without it feeling hand-wavy?
Thanks @lucaferri, you captured exactly what I was trying to get at with the “invisible tax” idea.
That’s been my experience too. The danger isn’t the big failure, it’s the normalization of friction. When everything feels just a bit slower, a bit harder, a bit riskier, teams stop questioning it. It becomes “just how things are.”
I especially like your framing of making the interest visible as a percentage cost. That shift from “messy code” to “ongoing business expense” is powerful, because it moves the conversation out of opinion and into tradeoffs.
To your question, yes, but I’ll be honest, it’s never perfectly precise, and trying to over quantify it can backfire. What I’ve found works is a mix of lightweight signals rather than a single “number”:
Individually, each of these is a bit fuzzy. Together, they tell a story that’s hard to ignore.
Sometimes I’ll even frame it narratively rather than numerically:
“This feature took 3 days. In a healthier part of the system, it likely would’ve taken 1.”
It’s not scientifically exact, but it’s concrete enough for stakeholders to grasp the cost.
The key, I think, is consistency, not proving the exact interest rate, but repeatedly showing that the same areas incur the same kind of drag. Over time, that pattern builds trust and makes the repay vs defer conversation much easier.
And you’re absolutely right, most teams don’t decide to carry high interest debt, they just underestimate how quickly “later” arrives.