I once worked with a guy named Ev, who is quite possibly one of the most prolific yet worst programmers I’ve ever worked with. During a review of something he implemented, another guy on the team asked “Who is benefitting from all of this code? Are we trying to please the gods of programming by offering them a sacrifice of so many lines of code?”
Following up after him was demoralizing. His implementations were often done out of band and just sort of wedged into production. Most of his work had to be completely rewritten, and the replacement process took far longer than just doing it correctly the first time. Nearly everything else had to be decommissioned, often with apologies to the internal stakeholders and clients.
Now, many people would think of this as just technical debt. After all, we need to move fast and break things! That’s the agile way, right?
But as I started asking questions and looking closer, I realized that wasn’t the whole story. Yes, there was some debt. But the overwhelming problem wasn’t debt; it was just a mess. They hadn’t made strategic trade-offs; they had just been… sloppy.
For years, I’ve seen teams and leaders use the term “technical debt” as a catch-all, a convenient excuse for any and all software problems. It’s a comforting, almost professional-sounding term for a codebase that’s hard to work with. But this confusion is dangerous. It masks the real issues, prevents us from having honest conversations, and ultimately, keeps us from building better software and healthier teams.
If we want to build excellent products, we need to be precise with our language. We need to differentiate between the calculated risks we take and the unforced errors we make. Because you can’t fix a problem you can’t properly name. One is a strategic loan; the other is a skill and process issue. And they require entirely different solutions.
The Strategic Loan
Let’s start with what technical debt actually is. The metaphor was coined by the brilliant Ward Cunningham, one of the authors of the Agile Manifesto. He wanted to explain to non-technical stakeholders the trade-offs involved in software development.
He said shipping new code is like taking on financial debt. You can take out a loan to get something valuable now—like a house, or in our case, a feature launched into the market—that you couldn’t afford to build perfectly upfront. In return, you pay interest. In the software world, that “interest” is the extra effort it will take to make changes in the future because of the shortcut you took today.
This is the key: Real technical debt is a conscious, strategic decision. It’s a tool. It’s choosing to do something imperfectly on purpose because the business need for speed outweighs the need for perfection at that moment.
Think about a startup racing to find product-market fit. You have a brilliant idea for a new feature, but building the “perfect,” infinitely scalable version would take six months. Your competitor is launching something similar in two. Do you spend the six months building the Taj Mahal while they capture the market? Of course not.
You take on debt.
- You might use an off-the-shelf solution for your billing system instead of building a custom one. You know it won’t handle complex enterprise plans, but right now, you only have ten customers paying a simple monthly fee. That’s a smart trade-off.
- You might write a data query that’s inefficient. You know it won’t scale to a million users, but today you have a thousand. Getting the feature out and validating the idea is more important than optimizing for a future you haven’t earned yet.
- You might hardcode a value that you know will eventually need to be a dynamic setting. But for the MVP, that value is fixed. It’s faster to type
const shippingCost = 5;
than to build a whole settings interface for the admin.
In all these cases, you are making a calculated bet. You’re sacrificing future ease of development for present-day velocity. Like any loan, it’s not inherently good or bad—it’s a financial instrument. Used wisely, it can be a powerful engine for growth. You get feedback faster, you learn from real users, and you can outmaneuver the competition.
The important part of this contract, however, is the “repayment” plan. A smart team not only recognizes when they are taking on debt but also tracks it. They know which parts of the system have “loans” against them and have a plan to “refactor” or “pay it back” when the time is right—perhaps when the feature is validated, when they secure more funding, or when the “interest payments” (the slowdown in development) become too high.
Thinking Of The Future
When you’re writing code, even if you can’t implement the perfect version of it, you can still build something that is eventually upgradeable. Build thoughtful interfaces so that the implementation can eventually change, be consistent in your conventions so that Future You can find and replace what you did, and don’t overarchitect so that it’s easy to load everything into your brain.
Just because you’re incurring debt doesn’t mean that it has to be poorly implemented. Think of it more like a partial or preliminary implementation, not a low quality one.
Slop Mode
Now, let’s talk about the other thing: slop.
Sloppy implementation is not a strategic choice. It is a failure of execution. It is poorly written, confusing, and bug-prone code. It’s building something in a way that is not just imperfect, but unprofessional.
- Variables named
x
,data2
, ortempThing
aren’t a shortcut; they are a sign of carelessness that will confuse the next person who reads the code (which might be you in six months). - Copy-pasting the same ten lines of code in five different places isn’t a clever hack; it’s a ticking time bomb that ensures when a bug is found in one place, it will likely go unfixed in the others.
- Ignoring established design patterns and principles not because you have a better idea, but because you don’t know them, isn’t a trade-off; it’s a skill gap.
Unlike strategic debt, sloppiness doesn’t speed you up. In fact, it almost always slows you down, even in the short term. It’s like trying to cook a meal in the messy kitchen. You can’t find the right utensils, you keep slipping on mystery spills, and you’re constantly worried about giving someone food poisoning.
So, why does this happen? It’s rarely because developers are lazy or malicious. My work as a consultant has shown me that sloppiness is usually a symptom of deeper issues within the team and the organization:
- Lack of Knowledge or Experience: Sometimes, a developer simply doesn’t know a better way. They might be junior, or they might be unfamiliar with a particular language or framework. They aren’t choosing a suboptimal path; they are just doing the best they can with the knowledge they have. This is a training and mentorship problem.
- Psychological Pressure and Cognitive Load: Software development is an intensely mental activity. When developers are under immense pressure to “just get it done,” their cognitive load skyrockets. Our brains have a finite capacity for decision-making. Under stress, we fall back on what’s most familiar and easiest, not what’s best. We skip writing the test, we don’t take the extra ten minutes to think of a clear name, we create a messy interface because our brain is too fried to think about proper abstraction.
- No Clear Standards or Processes: Without a shared understanding of what “good” looks like, every developer is left to their own devices. This leads to a chaotic mix of styles and quality levels. Things like code reviews, automated style checkers, and a clear “Definition of Done” aren’t bureaucracy; they are the guardrails that protect the team from its own worst impulses on a bad day.
- A Culture of “Good Enough”: This is the most insidious cause. It’s a team or company culture where corners are constantly cut, where “just ship it” is the only mantra, and where there’s no psychological safety to push back and say, “This isn’t right.” When developers feel like their craft isn’t valued, they stop valuing it themselves. They stop cleaning the kitchen because they know no one else cares, and another party is just going to mess it up tomorrow.
The Peril of Conflation
When we call a sloppy mess “technical debt,” we do two very damaging things.
First, we legitimize poor work. We give it the respectable, strategic-sounding name of “debt.” This allows teams to avoid accountability. Instead of having a tough conversation about skill gaps, bad habits, or a broken process, everyone just shrugs and adds it to the “tech debt” backlog, where it often languishes forever. I once worked with a team where the lead developer would dismiss any criticism of his convoluted code by saying, “Yeah, it’s just some tech debt we had to take on to meet the deadline.” In reality, the deadline was reasonable; his approach was just needlessly complex. By calling it debt, he avoided any personal responsibility to learn a simpler, cleaner way.
Second, we prevent ourselves from applying the right solution. You don’t fix a leaky faucet by taking out a loan, and you don’t fix sloppy code with the same tools you use to manage strategic debt. Trying to “pay down” a mess by simply allocating 20% of a team’s time to it doesn’t work if the team doesn’t have the skills or the standards to produce cleaner code in the first place. They’ll just create a different mess.
This confusion creates a vicious cycle. The codebase gets worse, development slows to a crawl, and frustration mounts. The business loses trust in the engineering team because they can’t deliver features predictably. The engineers lose pride in their work because they’re constantly fighting fires in a system they know is broken. This is how projects fail. Not with a bang, but with the accumulated weight of a thousand sloppy decisions.
How to Move Forward
As an advisor, my job is often to come in and help teams clean up the kitchen. This means helping them distinguish between their loans and their messes and creating a plan to tackle both. It’s not just about the code; it’s about the people and the process.
Part 1: Managing Your Strategic Debt
For the debt that was taken on intentionally, you need a clear-eyed financial plan.
- Make it Visible: Don’t let debt be an invisible specter. Create a “Technical Debt Registry” in your project management tool. When a conscious decision is made to take a shortcut, document it. What was the shortcut? Why was it taken? What is the “interest payment” (e.g., “This will make it hard to add new payment providers”)? And what is the trigger for repayment (e.g., “When we onboard an enterprise customer”)?
- Prioritize Repayment: Not all debt is created equal. Sit down with the product and business leaders. Explain the “interest” in terms they understand. “Because we used this shortcut, adding a new international shipping option will take three weeks instead of one. Is that a cost we’re willing to keep paying?” This helps frame the conversation around business impact, not just technical purity.
- Schedule Payments: Don’t just hope you’ll have time to fix it later. You won’t. Formally allocate time for it. This could be dedicating every fifth sprint to refactoring and debt pay-down, or reserving a percentage of every sprint’s capacity (e.g., 20%) for this work. Treat it like a non-negotiable business expense, like paying your rent.
Part 2: Cleaning Up the Sloppy Mess
This is the harder part. It requires changing habits, improving skills, and sometimes, shifting the entire team culture.
- Set a Quality Standard (and Automate It): Your team needs a shared, explicit definition of what good code looks like. This isn’t about personal preference; it’s about professional standards. Agree on coding conventions, and then enforce them with automated tools (like linters and formatters) that run before code can even be checked in. This removes the “style police” from code reviews and makes the standard objective, not personal.
- Insist on Rigorous Code Reviews: A code review is not a formality. It is your primary tool for quality control and knowledge sharing. A good review checks for more than just bugs; it looks for clarity, simplicity, and maintainability. Foster a culture where this is seen as a supportive, collaborative process, not a judgmental one. The standard should be, “Would I be proud for anyone on the team to maintain this code in a year?” Note: this does not have to be the now-common “pull request-style” of code review—there are many kinds!
- Lead with Humility and Create Psychological Safety: As a leader, you must model the behavior you want to see. Admit when you don’t know something. Be open to feedback on your own ideas. Create an environment where a junior developer feels safe to question a senior developer’s approach without fear of reprisal. When people feel safe, they are more willing to take the time to do things right and to point out when things are going wrong.
- Invest in Your People: If the problem is a skill gap, address it head-on. This could mean pair programming, where junior and senior developers work together at the same keyboard. It could mean dedicated training time, online courses, or bringing in an expert for a workshop. An investment in your team’s skills pays the highest dividends in the long run.
The North Star: Building with Intention
Building great software is about more than just making things work. It’s about building things that are built to last, that are a pleasure to work on, and that can evolve with the needs of the business.
This starts with being honest with ourselves. It starts with having the clarity to distinguish between a deliberate, strategic choice and a simple, unforced error. One is a calculated risk that can propel a business forward. The other is a silent killer that erodes morale and productivity.
So, take a look at your own kitchen. Is it sparkling clean? Probably not, and that’s okay. A little bit of controlled mess is the sign of a kitchen that’s being used. But is it a place where you can cook efficiently and safely? Do you have some well-understood “loans” you’ve taken out to get dinner on the table faster, with a plan to clean up later? Or is it a disaster zone where every action is a struggle and you’re afraid to open the refrigerator?
Stop calling every problem “technical debt.” Call the mess a mess. And then, as a team, roll up your sleeves, grab a sponge, and start cleaning. Build with intention. Your team, your product, and your future self will thank you for it.
Leave a Reply