HM NayemHM Nayem
All blogs

What founders get wrong about technical debt

·19 min read

Technical debt gets talked about as if it is always bad, a mess to be ashamed of and eliminated as fast as possible. That framing is wrong, and it leads founders to make worse decisions, not better ones. Technical debt is a tool, like financial debt, and the question is never simply whether you have it but whether you took it on deliberately and whether you can afford the payments. Most of what founders believe about it is either too fearful or too dismissive. Here is what they tend to misunderstand, the concrete places it actually shows up, and how to think about all of it in a way that helps rather than scares.

Debt is not the same as bad code

The first confusion is treating all technical debt as bad code. It is not. Bad code is code written carelessly by people who did not know better or did not try. Technical debt, properly understood, is a deliberate choice to do something the quick way now in exchange for a cost later, made consciously because moving fast was worth more than moving cleanly at that moment. A startup that takes on debt to ship quickly and capture an opportunity is often making a smart decision. The problem is not debt. The problem is debt taken on accidentally, without anyone deciding to, which is really just mess wearing a respectable name.

This distinction matters for founders because it changes what you do about it. If you think all debt is bad code, your instinct is to demand cleaner work and feel let down by your engineers. If you understand that most healthy debt is a conscious trade, your instinct is to ask a better question: what did we borrow, why, and when do we pay it back. One instinct creates blame. The other creates a plan.

The debt metaphor is actually useful

The comparison to financial debt is worth taking seriously, because it is genuinely instructive. Debt can be a smart move: you borrow against the future to do something valuable now that you could not otherwise afford. It becomes dangerous when you take on more than you can service, when you forget you have it, or when the interest quietly compounds until it consumes you. Technical debt works the same way. Taken deliberately and managed, it is leverage. Ignored and accumulated, it is the thing that slowly makes every change harder until the team can barely move. The metaphor tells you the right questions: did we choose this, do we know how much we have, and can we afford the interest.

The interest is the part founders underestimate. With a shortcut, you do not pay the cost once. You pay a little bit of it every single time you touch that part of the system afterward. A hack that saved you a week can cost you two days a month for a year, and you never see the bill arrive in one piece, so it feels free right up until the team is spending half its time fighting the code instead of building.

Where technical debt actually comes from

Founders often imagine technical debt as one big thing, a vague cloud of "messy code" somewhere in the product. In reality it is many specific things, most of which look completely reasonable at the moment they are created. It helps to name them, because you cannot manage what you cannot see. Here are the common sources, and then a closer look at the ones that hurt the most.

  • MVP shortcuts that were never revisited
  • "Temporary" hacks that quietly became permanent
  • Hard-coded values that should have been configurable
  • Missing or thin automated tests
  • Copy-pasted code repeated in many places
  • Tightly coupled systems where everything depends on everything
  • Infrastructure work deferred until "later"
  • Outdated dependencies and unpatched versions rotting in place
  • One engineer who is the only person who understands a system
  • Migrations postponed past the point they were easy
  • Security shortcuts taken to ship on time
  • Data model mistakes that get more expensive every month
  • Deep dependence on a single third-party vendor
  • No monitoring, so you find out about problems from customers
  • Undocumented systems only the original author can navigate
  • AI-generated code shipped without anyone truly reviewing it

Most of these are not signs of a bad team. They are the ordinary residue of moving fast under real constraints. The goal is not to have none of them. It is to know which ones you are carrying, and which ones are quietly getting more expensive.

The MVP shortcut that never got revisited

Every early product is built on shortcuts, and it should be. When you are validating whether an idea works at all, building it properly would be premature, because you do not yet know if the thing should exist. So you wire it together fast. The trouble is that "we will clean this up once it works" almost never happens on its own, because once it works there is always a more urgent thing to build. The MVP that was meant to be thrown away becomes the foundation the whole company stands on.

This is not a reason to over-build the MVP. It is a reason to be honest that the shortcut is now load-bearing. The founders who get burned are the ones who forget the early version was a sketch and start treating it as if it were architecture. The ones who do well remember what they skipped and schedule the real version before the sketch cracks under weight.

The "temporary" hack that stayed for years

Related, and worse, is the temporary fix. Something breaks, a customer is angry, and an engineer puts in a quick patch to stop the bleeding, fully intending to do it right next week. Next week never comes. The patch sits there for three years, and by then other things have been built on top of it, so removing it is no longer a small job. The word "temporary" in software is one of the least reliable words there is.

The plain business version of this: every temporary fix is a promise to your future self, and your future self is busy and will not remember. The fix is not to ban temporary fixes, because sometimes stopping the bleeding is exactly right. The fix is to write the temporary hacks down somewhere visible, so that "temporary" has a chance of actually being temporary instead of becoming a permanent resident nobody remembers inviting.

Hard-coded values and the config you forgot to make

Early on, engineers hard-code things because it is faster. The price of a plan is written directly into the code. The list of allowed countries is a fixed set typed into a file. A customer's specific business rule lives in a line of logic instead of a setting. This is fine until the day you want to change the price, add a country, or serve a second customer with a different rule, and then a change that should take a marketing person thirty seconds requires an engineer, a code change, and a deployment.

Think of it like a shop where the prices are painted onto the walls instead of printed on tags. It works when you have one price and one wall. The moment you want a sale, or a second location, you are repainting. Hard-coded values are cheap to create and expensive to live with once the business needs to move faster than the engineering team can ship changes.

Missing tests and copy-pasted code

Two of the quietest sources of debt are the absence of automated tests and the habit of copy-pasting code. They deserve to be understood plainly, because they sound like engineering hygiene and are actually about business speed.

  • Missing tests mean nobody knows if a change broke something until a customer finds out.
  • Copy-pasted code means one fix has to be made in five places, and someone always misses one.

Automated tests are a safety net that lets the team change things quickly and confidently. Without them, every change is a gamble, and the team either moves slowly out of fear or moves fast and breaks things in production. Neither is cheap. Copy-pasted code has the same shape of problem: the same logic lives in many places, so when the rule changes you have to find and update every copy, and the one you miss becomes the bug you ship. Both feel like they save time in the moment. Both quietly tax every future change.

Tightly coupled systems, where everything touches everything

As a product grows, the pieces start to depend on each other. That is normal. It becomes debt when the dependencies get so tangled that you cannot change one part without risking every other part. An engineer goes to adjust the checkout and accidentally breaks the email system, because somewhere those two things were wired together in a way nobody documented.

The useful image is a house where the plumbing, wiring, and gas lines all run through the same pipe. Touch one and you risk all three. Tightly coupled systems are why a small requested change sometimes comes back with a shocking time estimate. The engineer is not padding. They are telling you that the part you want to change is knotted into six other parts, and untangling it safely is the real job.

Deferred infrastructure and the scaling wall

Infrastructure is the plumbing under the product: the servers, the databases, the systems that keep everything running as usage grows. It is easy to defer, because when you have a hundred users, the cheap and simple setup works fine and building for scale would be a waste. The debt shows up as a wall. Growth arrives, the simple setup that was perfectly adequate suddenly buckles, and you are trying to rebuild the foundation while the house is full of people.

I have lived on both sides of this. When I rebuilt an outdoor-lighting company's platform onto a serverless setup, a large part of the work was undoing infrastructure decisions that were reasonable when traffic was small and painful once it was not. The lesson is not to build for a million users on day one. It is to notice the wall coming while you still have room to move, rather than discovering it the week your product finally takes off.

Dependency and version rot

Software is built on other software. Your product sits on top of dozens or hundreds of outside components, and all of them keep releasing new versions, fixing bugs, and closing security holes. If you never update them, you are not staying still. You are falling behind, quietly, while the ground shifts under you. This is version rot, and it is one of the most invisible forms of debt because nothing appears to be wrong right up until it is.

The bill comes due in two ways. Either a security hole is found in an old component and you are exposed because you never updated, or you finally have to update after years of neglect and discover that so much has changed that the update is now an enormous, risky project instead of a routine one. Keeping dependencies reasonably current is like servicing a car. Skipping it saves money now and costs much more when the engine seizes.

The bus factor, when one person is the whole system

Here is a kind of debt that has nothing to do with code quality and everything to do with people. Often one engineer builds a system, understands it completely, and becomes the only person who does. Everything works, so nobody questions it. Then that person goes on vacation, or gets sick, or leaves, and you discover that a critical part of your company lived entirely in one human's head.

This is sometimes bluntly called the bus factor: how many people would have to be hit by a bus before a system becomes unmaintainable. If the answer is one, you have concentrated risk that no code review will catch. The fix is not dramatic. It is having more than one person touch important systems, writing down how the critical parts work, and treating the knowledge in someone's head as an asset the company should not depend on a single copy of.

Postponed migrations and data model mistakes

Some debt is structural, baked into how your product stores and organizes information, and it is the most expensive kind to fix late. A migration is moving from an old way of doing something to a new one: a new database, a new version of a system, a new structure. Migrations are always easier when the system is small. Every month you postpone one, there is more data and more code depending on the old way, so the job that would have taken a week takes a quarter.

Data model mistakes are the sharpest version of this. The data model is the shape of your information: how a customer, an order, a subscription are defined and related. Get that shape wrong early, and everything you build sits on top of the wrong foundation. A decision that seemed harmless, like assuming each customer has exactly one account, becomes a wall the day a customer needs two, and by then thousands of records and dozens of features assume the old shape. Data model debt is worth more scrutiny than almost any other kind, because it is the one you can least easily walk back.

Security shortcuts and the vendor you cannot leave

Two more that live squarely in business risk rather than developer comfort.

  • Security shortcuts taken to hit a deadline
  • Third-party lock-in, where one vendor holds a piece you cannot easily replace

Security debt is the shortcut you take when shipping on time matters more than doing authentication, permissions, or data handling properly. It is silent debt, because nothing looks wrong until the day something goes very wrong, and then the cost is not measured in engineering hours but in customer trust, legal exposure, and sometimes the whole company. This is one of the few kinds of debt where "we will get to it later" is genuinely dangerous rather than merely expensive.

Third-party lock-in is subtler. You build deeply on one outside service because it is fast and convenient, and over time so much of your product assumes that service that leaving would mean rebuilding a large part of the system. That is fine when the vendor stays cheap and reliable. It becomes a problem when they raise prices, change terms, or degrade, and you find you have no leverage because you cannot realistically walk away. The debt is not using the vendor. It is having no path off them.

No monitoring, and no documentation

Two blind spots that make every other kind of debt harder to manage.

  • No monitoring or observability, so you learn about problems from customers
  • No documentation, so systems can only be understood by their author

Monitoring and observability are the instruments that tell you what your product is doing: whether it is up, how fast it is, where it is failing. Without them you are flying blind, and the first person to notice something is broken is the customer, which is the worst possible way to find out. Adding this is not glamorous work, but the difference between knowing a problem is coming and hearing about it from an angry user is enormous.

Documentation is the map of how things work. When systems are undocumented, all that knowledge lives in people's heads, which ties straight back to the bus factor and makes onboarding new engineers slow and painful. You do not need to document everything, and over-documentation rots too. But the critical systems, the strange decisions, and the things that would surprise a newcomer are worth writing down, because the alternative is paying for the same discovery over and over.

The new one, AI-generated code shipped without review

There is a fresh source of debt that did not exist a few years ago, and it is accumulating fast. AI can now write large amounts of code very quickly, and the temptation is to ship it as soon as it appears to work. The problem is that code which runs is not the same as code anyone understands. When AI writes a chunk of your product and no human truly reviewed it, you have shipped something nobody on the team can confidently explain, fix, or safely change later.

This is technical debt with a new label but the same shape. It is fast now and expensive later, and it stacks on top of every other kind: undocumented, because no one wrote it with intent; hard to maintain, because no one internalized how it works; and quietly risky, because the review step that would have caught the security shortcut or the data model mistake got skipped in the name of speed. AI is a genuine accelerator, and I use it heavily. But the review is not the slow part you skip to go faster. The review is the difference between borrowing deliberately and borrowing blind.

Talk about it in terms the business understands

One reason technical debt causes friction between founders and engineers is that they talk about it in different languages. Engineers say "we need to refactor" and founders hear "the team wants to spend time on something invisible instead of shipping features." The conversation goes better when debt is framed in business terms: this shortcut is now costing us roughly this much speed on every new feature, and spending this time to address it will get that speed back. Framed that way, paying down debt is not an engineering indulgence, it is an investment with a return the founder can actually evaluate.

The teams that manage debt well are usually the ones that learned to discuss it as a business tradeoff rather than a technical complaint. If your engineers only ever raise debt as "the code is bad," push them to translate it: which future work is this slowing down, by how much, and what does fixing it buy back. And if you are the founder, resist the urge to treat every such request as foot-dragging. Sometimes it is. Often it is your team telling you the interest rate is climbing.

The right time to take it on, and the right time to pay it down

There are moments when taking on technical debt is exactly right, and moments when refusing to pay it down is exactly wrong. Knowing the difference is most of the skill.

  • Take it on to validate an idea before investing in doing it properly.
  • Take it on when a real opportunity has a real deadline and shipping fast wins.
  • Take it on when you are early and uncertain and polish would be premature.
  • Pay it down when features consistently take longer than they should.
  • Pay it down when small changes keep rippling into big ones.
  • Pay it down when the team spends more time working around old shortcuts than building.

In the early, uncertain moments, the quick and imperfect version is the correct call, and refusing all debt out of engineering purity is its own mistake, one that trades real opportunity for tidiness no user will ever see. But debt becomes a problem when it is never repaid and the interest compounds. When servicing your debt starts eating a serious share of your team's capacity, it is time to pay some down. The mistake founders make is at both extremes: never paying it, letting it compound until the product is barely maintainable, or panicking and trying to eliminate all of it at once, which is usually a waste. The goal is not zero debt. It is a level of debt whose interest you can comfortably afford.

The healthy relationship with debt

The founders who handle this well do not treat technical debt as a moral failing to be eliminated, nor as a free lunch to be ignored. They treat it as what it is: a normal, useful, sometimes necessary tool that has to be managed. They take it on deliberately when speed is worth it, they keep track of where it is and what it is costing, and they pay it down when the interest gets high, not before and not never. When I built and repeatedly rebuilt Stack Learner, the ed-tech platform I founded, debt was a constant companion, taken on to move fast and paid down when it started to bite, and the skill was never avoiding it but managing it honestly.

That is the relationship to aim for. Not shame, not denial. Just a founder who knows exactly what they borrowed and what it costs.

HM Nayem
Founder @ Stack Learner · Director of Engineering @ Toptal
Work with me
Newsletter

Notes on building things that last

Occasional writing on product, engineering, and building a company, sent when I have something worth saying. No noise.

No spam. Unsubscribe anytime.