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:
- In code that rarely changes and works. An internal admin screen one person opens once a month. A data export that runs fine. A script nobody has touched in a year. Rewriting these is pure risk with no payoff.
- In a part of the product you might delete. If a feature is still being validated, polishing its internals is premature. Wait until you know it is staying. I make the same argument about scope in what to cut from an MVP.
- A matter of taste. Code that is written differently from how you would write it is not debt. Unfamiliar is not the same as wrong. If the only complaint is style, a linter or a formatter fixes it without a project.
- Isolated behind a clear boundary. A messy module with a clean interface is cheap debt, because the mess cannot leak. You can replace the inside later without touching anything around it.
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:
- Pay as you go, in the area you are already in. When a feature touches a messy module, the change includes one improvement to that module. Not a rewrite, one improvement: a test around the behaviour you are about to change, a duplicated calculation merged, a hardcoded value moved to configuration.
- Write tests before you refactor, not after. On a path you are about to change, a few tests that pin current behaviour turn a scary refactor into a safe one. The same idea drives regression testing for voice agents, where a frozen set of known calls is what makes a prompt change safe.
- Keep a short, written debt list with an interest note. Not a vague backlog item called "tech debt". A list where each entry says what it is, where it is, and what it costs us, for example "every new integration needs the same three manual steps". That makes prioritising it a normal product conversation.
- Review for new debt, not just old. Most debt enters through pull requests that nobody questioned. I wrote about what I look for in code review on a small team, and "does this make the next change harder?" is one of the questions.
- Treat AI-written code as debt until someone understands it. I use Claude Code heavily, and it writes good code fast. But code that nobody on the team can explain is a loan taken out on your behalf. The author still owns it, which is how I run Claude Code on client projects.
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:
- Every change in that area causes a regression somewhere unrelated.
- The part sits on a path that changes every week, so the interest is paid constantly.
- The original assumptions no longer hold, for example a single-platform design now serving three platforms.
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.