NNabeel Hassan

Blog · November 10, 2026 · 9 min read

Technical Debt in a Startup: What to Pay Down and What to Live With

By Nabeel Hassan — AI Engineer · ICPC World Finalist

TL;DR: Technical debt in a startup is not a moral failing and it is not something to "clear" in a dedicated sprint. It is a loan, and the only useful question about any piece of it is how much interest it charges you every week. Pay down debt that sits on a path you change often, debt that corrupts the facts your product cannot get wrong, and debt that makes releasing slow or scary. Live with debt in code that rarely changes, works, and is not on a critical path, even if it is ugly. Pay it down alongside feature work, roughly one fix per feature in the same area, and save full rebuilds for the rare part where the interest is clearly larger than the cost of replacing it. I have made these calls as CTO at RAQTS, Nystag Healthcare XR and Bettershop, and as an engineer at Geonode, and the rule that decides them is the same everywhere.

Founders usually raise technical debt in one of two tones. Either it is a vague worry ("the developers keep saying there is a lot of debt, should I be scared?") or it is a request for permission ("can we stop features for a month and clean things up?"). Both are reasonable. Neither is answerable until you stop treating debt as one big number and start treating it as a list of specific loans with specific interest rates.

What technical debt actually is

Technical debt is any shortcut that makes the next change harder than it would otherwise be. Some of it is deliberate: you hardcoded a configuration, skipped a test, or copied a function because shipping this week mattered more. Some of it is accidental: the product changed direction and the code still reflects the old shape. Some of it is just age: a library, an SDK or an engine version that was current two years ago and is now a liability.

The useful part of the metaphor is the interest. Code that is ugly but never touched charges no interest. You can carry it forever at zero cost. Code that is slightly messy but sits on the path every new feature goes through charges interest on every single change, in slower work, more bugs and more fear. That second kind is the expensive kind, and it often does not look as bad as the first.

So when someone tells me a codebase has "a lot of debt", my first question is never how bad the code is. It is where the code is, and how often we touch it.

The three kinds of debt worth paying down

In practice, almost all the debt I have decided to pay down fell into three groups.

1. Debt on the paths you change every week

Every product has a few areas that absorb most of the changes: the booking flow, the main screen, the API that every client calls, the integration that every new customer needs configured. If those areas are hard to change, every feature on the roadmap is a little more expensive, forever.

This is where refactoring pays back fastest, because the payoff shows up on the very next ticket. It is also why I look at what users and developers actually touch before deciding anything, which is the third step of the checklist in my first week with an inherited codebase.

2. Debt that corrupts the truth

Every product has a handful of facts it cannot get wrong. On a sports-tech platform it is a player's score and their position on a leaderboard. On an analytics dashboard for Amazon sellers it is the profit figure. On a voice agent system it is the lead record in the CRM.

Debt that lets those facts drift is in a category of its own, because the interest is not paid in developer time. It is paid in user trust. At RAQTS, the platform ran across a Unity layer, React Native apps and a Next.js portal, and the direction I set was that the backend owns the leaderboard ordering and every client only renders it, which I wrote about in stepping in as CTO mid-build. At Bettershop, the expensive decision was the data model behind profit, covered in building a GraphQL admin dashboard for Amazon sellers. Neither of those was the ugliest code in the product. Both were the most important debt to get right, because when two screens disagree about a number, users stop believing either of them.

3. Debt that makes releasing slow or scary

If shipping a fix takes a day of manual steps, or only one person knows how to produce a signed build, every other problem gets worse, because you cannot fix anything quickly. Release debt multiplies all the other debt.

This is the debt I pay down first, almost without exception. At Geonode I implemented Unity and Windows CI/CD pipelines for exactly this reason, and I wrote up why in building a Unity CI/CD pipeline. On mobile this matters doubly, since a broken release sits on phones until store review lets the fix through.

The debt you should live with

The other half of the job is saying no to cleanup that does not earn its cost. I am comfortable leaving debt alone when it is:

Dependency debt: the kind that charges interest whether you touch it or not

There is one exception to "untouched code costs nothing", and it is dependencies. An engine version, an SDK, a framework or a platform API can go stale while your own code sits perfectly still, and then a store policy change or a security issue forces an upgrade on someone else's schedule.

Unity projects are the clearest example I know. Skip a few engine versions and an upgrade stops being an afternoon and becomes a project, with every plugin and package moving at once. I covered how I approach that in upgrading the Unity version on a live project. Native mobile plugins are similar: the iOS and Android sides each move on their own schedule, which is part of why Unity native plugins need an owner. On standalone headsets like the Vive Focus 3, where I led the clinical eye-tracking work at Nystag, the vendor SDK itself is a dependency you cannot ignore for long.

My rule for dependency debt is small and often. Upgrading one minor version a quarter is cheap. Upgrading four major versions under deadline pressure is one of the most stressful things a small team can do.

How I pay it down without stopping the roadmap

I almost never recommend a "debt sprint" where features stop for a month. It sounds disciplined, but it usually produces a large, hard-to-review change set in exactly the areas that are most fragile, with no user-visible result for the founder to judge. Instead:

When a rebuild is actually the right call

Sometimes the answer really is to replace a part. I consider it when the interest on a piece of debt is clearly larger than the cost of a rebuild, and when I can name what "done" looks like. Signals I take seriously:

Even then, I prefer replacing one boundary at a time behind the existing interface over a big-bang rewrite. The old version keeps running until the new one has matched its behaviour on real traffic. A full rewrite started without understanding why the old code is the way it is will faithfully reproduce the bugs the original team already fixed, minus the fixes.

The short version for founders

You do not need to understand the code to manage technical debt well. You need your engineers to answer three questions in plain language: which debt sits on paths we change often, which debt could make our numbers wrong, and which debt makes releasing slow. Fund those, steadily, inside normal feature work. Leave the rest alone unless it starts charging interest.


I work as a hands-on fractional CTO and engineer for startups in the US, UK and Europe, across web, mobile, Unity and XR, and AI voice agents. If your team keeps talking about technical debt and you want a clear read on what to fix and what to leave, more about how I work as a fractional CTO is here, or book a call.

FAQ

How do you decide which technical debt to pay down first?

By the interest it charges, not by how ugly the code is. Pay down debt on the paths you change most often, debt that lets important facts like scores, profit figures or lead records drift between screens, and debt that makes releasing slow or risky. Release debt usually comes first, because it makes every other fix slower.

Should a startup stop feature work for a technical debt sprint?

Rarely. A month-long cleanup tends to produce a large, risky change set in the most fragile areas with nothing visible to show for it. It works better to pay debt down alongside features: when a feature touches a messy area, the same change adds a test or makes one improvement there.

Is it ever fine to leave bad code alone?

Yes. Code that works, rarely changes, sits off the critical path, or is isolated behind a clean interface charges almost no interest. Code that is simply written differently from how you would write it is not debt at all. The exception is dependencies, which go stale even when your own code does not change.

When is rewriting part of an app the right call?

When the cost of keeping it is clearly higher than replacing it: changes there keep breaking unrelated things, it sits on a path you change every week, or its original assumptions no longer hold. Even then, replace one boundary at a time behind the existing interface and keep the old version running until the new one matches it on real traffic.

Building something in this space?

I take on AI-agent, automation and product work directly — scoped fast, shipped fast.

Book a discovery call →

Keep reading