
I’ve sat in that meeting.
The codebase is four years old. React 16. A monolith that everyone now wishes was microservices. An API that returns XML instead of the GraphQL everyone prefers.
In sprint planning, someone calls it “technical debt.” The phrase lands with a familiar weight. Everyone nods. We should pay it down. We’ll create tickets. We’ll allocate time. We’ll get to it.
We won’t.
I know we won’t because I’ve written those tickets. I spent six months maintaining a backlog of “debt reduction” items that I knew would never be prioritized. When someone asked why the migration wasn’t happening, I could point to the backlog. See? We’re tracking it. We have a plan.
We didn’t have a plan. We had a filing system for things we’d agreed to stop thinking about.
We call it debt because the metaphor is comforting. Debt implies a loan. A loan implies intention. Intention implies a plan to repay. We borrowed time from the future, and someday we’ll settle the account.
But that’s not what happened. Nobody borrowed anything. The engineers who wrote that code made reasonable decisions under constraints that made sense at the time. The constraints changed. The people who understood those constraints left. The company grew into something the architecture was never designed to support.
This isn’t debt. It’s archaeology.
What Ward Actually Meant
Ward Cunningham coined the term “technical debt” in 1992. He was building financial software in Smalltalk and needed to explain refactoring to his boss. A fiscal metaphor fit the audience.
But Ward’s original meaning was narrow and specific. He was describing what happens when code doesn’t reflect your current understanding of the problem. You learn something new about your domain, and the code hasn’t caught up yet. The “debt” is the gap between what you now know and what the code embodies.
This is not what we mean when we use the term today.
Today, “technical debt” has become a catch-all for anything we don’t like about the codebase. Legacy frameworks. Missing tests. Inconsistent naming conventions. That weird module nobody wants to touch. Code written by people who left. Code written under deadlines we’ve forgotten. Code that works fine but doesn’t match current fashion.
Ward himself has expressed regret. He reportedly wished he’d coined “opportunity” instead of “debt”: good practices produce opportunity, and opportunity can be consumed for short-term goals. That framing would have been harder to abuse.
The Comfortable Lie
The debt metaphor persists because it serves organizational purposes that have nothing to do with accuracy.
For engineers, “technical debt” provides vocabulary to communicate with business stakeholders who don’t understand code. We’re not asking for time to fiddle with things we don’t like. We’re paying down debt. The language borrows legitimacy from finance.
For managers, the metaphor provides cover. If the codebase is struggling, it’s because of accumulated debt, not because of decisions they made about staffing, timelines, or priorities. The debt just… happened.
For everyone, the metaphor implies eventual resolution. Debt gets paid. That’s what debt does. Someday we’ll have a “tech debt sprint” or a “refactoring quarter” and the slate will be clean.
But here’s what actually happens: the company makes a rational decision that the cost of “paying back” the debt exceeds the benefit. They’re usually right. And so the “debt” remains, accumulating interest that nobody ever intended to pay, until the system becomes so calcified that the only options are rewrites or replacement.
The debt metaphor lets us pretend this is temporary. It isn’t.
What the Lie Cost Us
I watched a team die on the debt metaphor.
They’d inherited a system nobody understood. The original architects had left. The documentation was sparse. The code worked, but nobody could explain why certain modules existed or what would break if they were changed.
For two years, they called it technical debt. They created epics. They estimated story points. They presented roadmaps to leadership showing how they’d “pay it down” over six quarters. Leadership nodded. The roadmaps looked responsible.
Nothing got paid down. Every quarter, new features took priority. The debt items rolled forward, quarter after quarter, until the backlog became a graveyard of good intentions.
Then the system started failing in production. Not catastrophically, just enough to erode trust. Response times crept up. Bugs appeared in places nobody had touched. The team spent more time fighting fires than building.
By year three, the best engineers had left. They were tired of working in a codebase that felt like quicksand, tired of promising improvements that never came, tired of the lie that someday things would get better.
The system wasn’t replaced. The company pivoted to a different product line. The codebase was quietly deprecated, and the remaining team was reassigned.
Nobody called it a failure. They called it “strategic reallocation.” But everyone knew: the system hadn’t died of technical debt. It had died of two years spent pretending that naming the problem was the same as solving it.
What We Actually Have
Strip away the metaphor and look at what remains.
You have code written under constraints that no longer apply. The startup needed to ship in six weeks or miss its funding window. The team had three engineers instead of ten. The requirements were for 1,000 users, not 100,000. React 16 was current. GraphQL wasn’t mainstream. Microservices were considered premature optimization for your scale.
None of these were mistakes. They were reasonable decisions made by reasonable people facing real constraints. The decisions worked. The company survived. Now the constraints have changed, and the code reflects a world that no longer exists.
Your codebase is a dig site. Each layer encodes what was true when it was written. The if-then-else statements scattered throughout? That was the fastest path to supporting French when a sales opportunity appeared. The manual data process that everyone hates? Budget for automation didn’t exist when it was built. The Microsoft stack that the team would rather replace? An enterprise licensing deal made it effectively free in 2019.
I call these “context loss events”: moments when critical knowledge leaves the organization, often when key people depart. After a context loss event, unfamiliar parts of the codebase become dark and scary. The code still works. But nobody remembers why it works that way, what constraints shaped it, what disasters it’s preventing.
Calling this “debt” implies negligence. It implies someone should have known better. It implies the code is wrong.
Often the code isn’t wrong. It’s just old. The context it was written for has evaporated, and we’re left holding artifacts from a civilization we no longer understand.
The Harder Conversation
The debt framing lets us avoid a harder conversation: this system was designed for a company that no longer exists.
Your monolith was built when you had one product and twenty engineers. Now you have five products and two hundred engineers. The architecture isn’t “debt.” It’s a structural mismatch between what you built for and what you’ve become.
Your XML API was built when your primary consumers were enterprise systems that expected XML. Now your consumers are mobile apps that expect JSON. The API isn’t “debt.” It’s a relic of a customer base you’ve outgrown.
Your test coverage was acceptable when releases happened quarterly. Now you deploy continuously and the gaps hurt. The missing tests aren’t “debt.” They’re evidence that your delivery model evolved faster than your practices.
These are not problems you solve by “paying down debt.” They’re problems you solve by acknowledging that the company transformed and the systems didn’t transform with it. That’s not negligence. That’s just what happens when organizations grow faster than their infrastructure.
The question isn’t “how do we pay this down?” It’s “what would we build if we were starting today?”
And then: what’s the realistic path from here to there? Not the path we’ll never take, but the one we might actually walk.
The Naming Trap
Here’s what I’ve come to believe: the moment you call something “technical debt,” you’ve already decided not to fix it.
The label feels like action. You’ve identified the problem. You’ve categorized it. You’ve added it to a system that tracks things. The organizational anxiety about the codebase now has a container.
But the container is a trap. Once something is “debt,” it enters a queue that never empties. It competes for prioritization against features that generate revenue, bugs that affect customers, and initiatives that executives care about. The debt loses every time. Not because it’s unimportant, but because the framing has already positioned it as deferrable.
The things that actually get fixed are never called debt. They’re called blockers. Emergencies. Strategic priorities. Customer escalations. The language signals urgency. The language demands action.
“Debt” signals patience. And patience, in organizations, is another word for never.
The Machine That Prints Debt
Everything above was true before AI. Now it’s worse, and the debt metaphor has become genuinely dangerous.
By late 2024, developers using GitHub Copilot were accepting AI-generated suggestions for roughly 40% of their code. Copilot, Cursor, Claude, ChatGPT — developers paste prompts and get working code back in seconds. The code compiles. The tests pass. The PR gets approved. Nobody asks where it came from or whether anyone understands it.
GitClear analyzed 211 million changed lines of code across five years and found that refactoring — the act of improving code without changing its behavior — dropped from 25% of all changes to under 10%. Meanwhile, copy-pasted code blocks surged, with duplicated blocks of five or more lines increasing eightfold during 2024. More code is being generated. Less of it is being properly restructured. The codebase gains mass without gaining integrity.
But here’s what makes the debt metaphor collapse entirely: debt implies a borrower who understood what they were borrowing. AI-generated code has no borrower. Nobody made a deliberate tradeoff. Nobody weighed the constraints. A developer asked a model for a function, got something that passed the tests, and shipped it. The “debt” was incurred by a process that doesn’t understand debt, accepted by a reviewer who didn’t write it, and inherited by a maintainer who can’t explain it.
We’re not accumulating debt anymore. We’re printing it. And we’re printing it faster than any human team could ever pay it back.
A 2024 study by Uplevel found that AI-assisted pull requests had a 41% higher bug rate than human-written code. The bugs aren’t the catastrophic kind that crash production. They’re the subtle kind: edge cases missed, error handling skipped, assumptions baked in that nobody documented because nobody made the assumption consciously. The kind of bugs that become archaeology in six months, when the developer who prompted the code has moved on and the model that generated it has been deprecated.
We used to accumulate technical debt through human decisions made under pressure. Now we generate it at machine speed, with no human understanding attached.
The old lie was “we’ll pay it back someday.” The new lie is “the AI will maintain what the AI wrote.” It won’t. AI generates code without context, without institutional memory, without understanding why the surrounding system works the way it does. When that code breaks, a human still has to fix it. And that human is staring at code that no person wrote, no person reviewed deeply, and no person can explain.
The debt metaphor assumed a human pace of accumulation. AI broke that assumption. We’re not in debt. We’re insolvent, and we’re still writing checks.
The System We Inherit
The engineer who inherits your codebase won’t see “technical debt.” They’ll see a system. They’ll try to understand why it works the way it does. They’ll find some of those reasons and miss others. They’ll make changes that make sense given what they understand, which is inevitably incomplete.
In five years, someone will inherit their code. The cycle continues.
This is not debt accumulating. This is how systems evolve. Each generation builds on incomplete understanding of the last. Knowledge is lost and recovered and lost again. Constraints change. The world moves. The code remains, carrying the weight of decisions it can no longer explain.
We can call this debt if we want. But the name doesn’t change the reality: we’re not going to pay it back. We never were.
The kindest thing we can do for the engineers who come after us isn’t to pretend we’ll fix it. It’s to write down why we built what we built, under what constraints, for what version of the company. Not because it will prevent future problems. But because it might help them understand the civilization they’ve inherited.
The next time you’re in sprint planning and someone calls something “technical debt,” notice what happens next. The nodding. The agreement. The tickets that get created. The collective relief that the problem has been named and filed.
Then ask yourself: did we just decide to solve this, or did we just decide to stop thinking about it?
Most of us already know the answer.
—Viz