Why “We’ll Fix It Later” Becomes “We’ll Never Fix It”
Technical debt isn't a coding problem, it's a culture problem. An exploration of why "fix it later" becomes "never", and what teams who escape this trap do differently.
Think about your last credit card statement. You see the balance, the interest rate, the minimum payment. Everything is visible and quantified. Technical debt doesn’t work like that.
That hardcoded configuration value sitting in your codebase? It’s not sending monthly reminders about how much time it costs every deployment. The integration you copied and pasted into three different services? It’s not calculating compound interest on the bugs you’ll need to fix in triplicate when something changes.
Here’s a sobering statistic: research shows that on average, 25% of development effort gets spent dealing with technical debt issues. That’s one out of every four hours your team works. Not building features. Not fixing customer-reported bugs. Just paying interest on past decisions.
When you take on technical debt intentionally (shipping a feature quickly to test market demand, for instance), you’re making a calculated trade. The problem is most technical debt isn’t intentional. It emerges from the gap between what you knew when you wrote the code and what you know now.
The Four Types of Technical Debt
Martin Fowler created a useful framework for thinking about this, called the Technical Debt Quadrant. It categorizes debt along two dimensions: whether it’s deliberate or inadvertent, and whether it’s reckless or prudent.
Reckless and deliberate debt happens when teams knowingly cut corners without considering consequences. “We don’t have time for design” shipped under deadline pressure with no plan to fix it.
Reckless and inadvertent debt comes from lack of knowledge. Junior teams building systems without understanding design patterns, creating problems they don’t even recognize as problems.
Prudent and inadvertent debt is what you learn by doing. You made the best decision you could with the information available, and only now (after building it) do you understand what you should have done differently.
Prudent and deliberate debt is the strategic kind. You know you’re taking shortcuts, you’ve weighed the tradeoffs, and you have a plan to address it. “We need to ship this MVP to validate the market, then we’ll refactor once we have funding”.
The issue is that most organizations treat all debt the same way, when these different types require completely different management approaches.
Consider this scenario: A junior engineer inherits a codebase with a cryptic comment: “DO NOT MODIFY - talks to legacy billing system”. Nobody remembers why. The person who wrote it left two years ago. The billing system was replaced eighteen months ago. But the warning remains, and with it, all the architectural decisions built around a constraint that vanished.
The debt compounds silently. Unlike financial debt, nobody’s tracking the balance until something breaks.
How Organizations Are Designed to Create Debt
Let me show you how this plays out. An engineer proposes refactoring a critical but fragile system. The work will take three weeks and deliver zero new features. Meanwhile, the roadmap has six features stakeholders are waiting for.
What happens next? The engineer who ships those six features gets recognized in the all-hands meeting. The engineer who prevented future incidents through careful refactoring gets nothing. You can’t celebrate disasters that didn’t happen.
Now extend this pattern across quarterly reviews, promotion cycles, and annual planning. What behavior gets rewarded? Shipping visible things. What behavior gets ignored or penalized? Investing time in invisible improvements.
Research confirms this intuition. Studies of technical debt across organizations consistently identify deadline pressure as the single most cited cause. The top three effects? Delivery delays, low maintainability, and constant rework. The very outcomes teams are trying to avoid by taking shortcuts are the outcomes they guarantee by taking them.
This raises an interesting point about job mobility. In a typical two to three year tenure at a company, you can easily ship code in year one and leave before the consequences arrive in year three. You’re incentivized to optimize for the demo, the launch, the promotion case. Not for the developer who’ll curse your name in 2027.
That’s not malice. It’s rational response to incentives.
Why Teams Can’t Talk About Technical Debt
Pay attention to how your team talks about technical debt. The language reveals deeper cultural problems.
In many organizations, acknowledging debt feels like confessing failure. “We need to fix this mess” carries an implicit “we screwed up”. This framing makes honest conversation impossible. The most damaging teams treat technical debt as a moral failing. Code is either “good” or “bad.” Developers who write imperfect code are careless or incompetent.
This creates a culture where everyone pretends their code is perfect, debt accumulates in silence, and nobody asks for time to fix anything because asking feels like admitting you’re not good enough.
Here’s what healthier teams recognize: all code is written under constraints. Time, knowledge, requirements, available tools. The code you wrote last year reflects what you knew last year. It’s not bad code. It’s old code.
That difference is profound. One framing breeds shame and hiding. The other breeds learning and improvement.
But even well-intentioned teams fall into linguistic traps. Calling it “technical debt” at all implies it’s the engineering team’s problem. In reality, it’s an organizational problem. Product managers who insist on impossible timelines create debt. Executives who refuse to invest in infrastructure create debt. Sales teams who promise custom features create debt. Yet the word “technical” assigns ownership to the people with the least power to prevent it.
The Hidden Hierarchy of Software Work
Let me describe a pattern you’ve probably seen. In most organizations, there’s a clear status hierarchy:
Building new products: prestigious
Adding features: valuable
Maintaining existing systems: thankless
Fixing old code: what you do when you’re not trusted with important work
This hierarchy pervades everything. Job postings celebrate “building from scratch”. Interview questions focus on greenfield design. Promotion packets highlight new systems launched. Meanwhile, the engineer who spent six months making the payment system reliable enough that nobody thinks about it? They get passed over because their work is invisible.
Technical Debt Lives Everywhere, Not Just in Code
Here’s another dimension to this: technical debt doesn’t just live in code. Research has identified at least ten distinct types of debt that accumulate across a system:
Design debt refers to architectural shortcuts and structural compromises. That microservice you made a monolith “just for now” because breaking it apart was too complex.
Code debt is the classic type—duplicated logic, tight coupling, complex functions that nobody wants to touch. The stuff that makes developers groan during code review.
Test debt accumulates when you skip writing tests or let your test suite become outdated and brittle. Every deploy becomes a gamble.
Documentation debt happens when your docs lag behind reality. The onboarding guide that references systems you deprecated six months ago. The API documentation that describes endpoints that don’t exist.
Infrastructure debt builds up when your deployment pipeline, monitoring, or server architecture can’t scale with your business. You’re still manually deploying because automating it keeps getting postponed.
Requirements debt emerges from partially implemented features or requirements that work for some cases but not others. The edge cases you said you’d handle “later.”
Architecture debt (distinct from design debt) involves fundamental technology choices that are now outdated or unsuitable. You’re stuck on an old framework version because upgrading would break everything.
The problem is that teams often only recognize and track code debt, treating it as the only “real” technical debt. Meanwhile, documentation debt makes onboarding take weeks instead of days. Test debt makes every release nerve-wracking. Infrastructure debt means your deploy process takes hours instead of minutes.
All of this debt is interconnected. Poor documentation makes it harder to refactor code. Lack of tests makes architectural changes risky. Infrastructure limitations prevent you from adopting better development practices.
Why Rewrites Almost Always Fail
When debt becomes sufficiently painful, teams reach for the most seductive solution: rewrite everything from scratch.
“This time”, they tell themselves, “we’ll do it right. We’ve learned from our mistakes. We understand the requirements now. The new version will be clean, fast, and maintainable”.
The rewrite almost never delivers on this promise. Joel Spolsky called rewriting from scratch “the single worst strategic mistake that any software company can make”, and yet teams make it constantly.
Want a historical example? The Year 2000 problem, where thousands of systems stored dates as two digits to save memory. What started as a reasonable optimization in the 1960s and 1970s turned into a crisis that cost an estimated $300 billion to fix as the year 2000 approached. That’s technical debt at civilization scale, accumulating over decades because “we’ll fix it later” never happened until it became an emergency.
Why do rewrites fail? Because they let you avoid confronting the hard organizational problems that created the debt in the first place. You don’t have to negotiate for refactoring time. You’ve declared the old system dead. You don’t have to gradually improve code while keeping features working. You’re building fresh. You don’t have to convince stakeholders that maintenance matters. You’ve framed it as innovation.
But here’s what doesn’t change: the organizational dynamics. The pressure to ship features quickly remains. The incentives rewarding visible work over sustainable work remain. The lack of time for documentation, testing, and thoughtful design remains.
So the rewrite proceeds under the same constraints that created the original mess. Worse, you’re now maintaining two systems: the old one that customers rely on and the new one that’s not ready yet.
Three years later, the “new” system is the legacy system. It has its own accumulated debt. And someone is proposing another rewrite.
What Teams Who Break the Cycle Actually Do
Some teams break the cycle. They’re the ones where “we’ll fix it later” sometimes actually means later. What do they do differently?
They Make Maintenance Continuous, Not Special
There are no “tech debt sprints” where refactoring gets quarantined away from real work. Instead, every project includes time for improvement. When you touch a part of the codebase to add a feature, you leave it slightly better than you found it. The boy scout rule isn’t a nice idea. It’s enforced practice.
This requires fighting the instinct to separate “feature work” from “cleanup work”. That separation seems efficient (focus on one thing at a time), but it ensures cleanup never happens. When maintenance is someone else’s job, it becomes nobody’s job. When it’s everyone’s responsibility as part of their normal work, it gets done.
They Build Feedback Loops That Create Accountability
If deployments are slow and error-prone, the team that writes code also handles deployments. If the system is hard to debug, the team that builds features also carries the pager. If technical decisions have consequences, the people making those decisions feel them directly.
This is why the DevOps movement matters beyond just eliminating silos. When developers operate what they build, they can’t externalize the cost of their technical decisions. The quick hack that’s annoying to deploy becomes their own problem. The missing observability that makes debugging hard wakes them up at 3am.
Suddenly, investing in quality becomes rational self-interest.
They Develop a Shared Language for Trade-offs
Successful teams don’t pretend that all technical debt is bad or that all maintenance is urgent. They get specific:
What breaks if we don’t fix this?
What becomes easier if we do?
What’s the time horizon on these consequences?
This specificity enables honest conversations with stakeholders. Instead of “we need tech debt time” you say “this system costs us two engineer-weeks per month in workarounds, and we can fix it in six weeks”.
That’s a trade-off product managers can evaluate. It treats engineering concerns as business concerns, because they are.
The Economics of Sustainability
Let me show you the math that changed how I think about this. The case for maintenance isn’t moral. It’s economic.
Organizations that chronically under-invest in code quality move slower over time, not faster. The quick hacks pile up. The workarounds compound. Eventually, every change requires touching six different systems, coordinating with four teams, and testing a dozen edge cases born from years of patches.
At some point (typically around year three or four of consistent neglect), teams hit a wall. Velocity plummets. Estimates balloon. Simple features take months. Engineers burn out from the constant firefighting. The backlog fills with bugs that can’t be fixed without refactoring, which there’s no time for, because you’re too busy fixing bugs.
This is when executives finally authorize “paying down technical debt” usually by pulling engineers off features for a quarter. It’s too little, too late. The debt wasn’t created in a quarter. It accumulated over years. A three-month sprint barely makes a dent.
Meanwhile, the feature roadmap stalls, pressure builds, and the moment the quarter ends, everyone races back to shipping features. Six months later, you’re back where you started, except with more debt.
The math is straightforward, but it requires believing in a time horizon longer than the next quarter. That’s the real barrier.
What You Can Do (Starting Tomorrow)
Here’s how to start shifting your team’s maintenance culture, even if you’re not in a leadership position:
Start tracking maintenance costs. Create a simple log of how much time the team spends working around technical debt each week. Make it visible. When stakeholders ask why a feature took longer than expected, point to concrete numbers. Remember: 25% of your team’s time is likely going to debt service. Make that visible.
Implement the boy scout rule locally. In your own pull requests, include at least one small improvement to existing code alongside your feature work. Make it a pattern others can see and copy.
Use TODO comments strategically. When you make a tradeoff or take a shortcut, document it explicitly: “TODO: Hardcoded for v1 launch, needs configuration system for multi-tenant” Make the debt searchable and trackable. Some teams do quarterly “TODO reviews” where they search the codebase and decide what still needs addressing.
Document the “why” aggressively. When you encounter code that’s hard to understand, add comments explaining the context once you figure it out. You’re not just helping future developers. You’re demonstrating what good documentation looks like.
Propose one protected maintenance hour per week. It’s harder to argue against one hour than against 20% of all time. Start small, show results, expand gradually.
Celebrate others’ maintenance work publicly. When a teammate fixes a longstanding issue or improves system reliability, call it out in standups or team chats. Culture shifts when the work you want to see becomes visible and appreciated.
Reframe conversations about speed. When pressured to take shortcuts, respond with “We can ship this in three weeks and spend two weeks per month maintaining it, or five weeks and spend zero time maintaining it. Which is faster over the next year?”
The choice isn’t between moving fast and writing quality code. It’s between moving fast now and being unable to move at all in three years.
What are you building for? The next demo, or the next decade?