When to pay debt, when to take it
Debt is a tool, not a sin.
Most technical-debt conversations I sit through don't end with a decision. Someone says we have too much technical debt, someone else answers that we are a startup and debt is the price of speed, and a third person proposes reserving twenty percent of every sprint for paying it down. Then the meeting ends, because it can: nobody in the room pays for the non-decision, the bill lands months later on whoever is in the code then, and meanwhile the codebase drifts in whichever direction the loudest engineer wanted it to drift.
None of the four frames in circulation helps much. Ward Cunningham coined the phrase "technical debt" in 1992 to explain to his boss why a financial application needed rework, and his actual point was narrow: the code reflected the team's earlier understanding of the domain, the understanding had moved, and the cost was the gap between the two. Most people have drifted from that point and use "technical debt" as a synonym for sloppy code, which collapses Cunningham's claim into a tone complaint.
Martin Fowler's quadrant of deliberate-vs-inadvertent crossed with prudent-vs-reckless is the most cited frame I see in engineering-management writing. It gives a vocabulary, but not a decision rule, and "be deliberate and prudent" survives any contact with a real planning conversation by saying nothing specific.
The startup-leverage school treats debt as financial leverage against future engineering hours, and it is correct in spirit and incomplete in practice, because nobody in it names which debts compound viciously and which decay harmlessly. The strong form of "ship faster, fix it later" produces what Fowler would call the reckless-and-deliberate quadrant while sounding like the prudent-and-deliberate one.
The fourth frame is the 20%-per-sprint rule, the standard playbook answer. Some teams pair it with the boy-scout rule, popularized by Robert C. Martin, where every engineer leaves any file they touch a little cleaner than they found it. The pairing answers how much, but it does not answer which.
The which question is the one a leader has to answer, and the seven decisions below are how I answer it across the take-and-pay cycle, in order, every time. Each one is a section of this essay.
Reject both frames
Two frames dominate every debt conversation I have been in: one treats all debt as moral failure, the other treats it as strategic leverage, and each of them, followed faithfully, produces reckless debt while sounding prudent.
The perfectionist frame argues for refactoring before shipping the next thing. This burns the cycles an early-stage product needs to discover what to build, and it rests on a smuggled assumption that good code is substantially slower to write, which in my experience it is not. The exception is a genuine sprint-to-survive moment, which is rare, and which the section on taking debt covers separately.
The leverage-maximalist frame argues that since debt is a feature, you should ship and not look back until you have product-market fit. It is right about the direction and wrong about the absence of any filter, and inside that gap teams accumulate the debts that kill them and skip the debts that are free to carry.
So the first decision is to refuse both frames out loud, in the planning conversation, before anyone reaches for a backlog item, and go straight to locating the debt.
Locate by layer, judge by reversibility
When debt comes up I make the team name the layer it sits in. The layers I keep returning to:
- Domain model. The concepts the system reasons in, how the problem is sliced. This is Cunningham's original debt layer.
- Technology stack. Languages, frameworks, primary database, infrastructure primitives.
- Architecture. Services, modules, boundaries, the shape of the system.
- Foundational patterns. How data flows, how data is stored, how dependencies are initialised, error handling, state management.
- Schema and data contracts. Migrations, indexes, denormalisations.
- Public API and integration contracts. Versioning, backward compatibility.
- Test scaffolding and observability primitives.
- Per-file code.
Naming the layer is necessary because it changes who has to be in the room, but it is not sufficient on its own, because the same layer can hold two very different debts. What separates them is reversibility.
On the first product I worked on, the team picked Angular for early-stage speed, and the choice was correct on the day it was made. Today it is too expensive to port out of:
- Hiring is harder because fewer candidates are fluent in it.
- Bundles are heavy.
- Performance has a ceiling we cannot raise inside the framework.
- A swap touches the whole organisation.
That debt is stack-layer.
In the same codebase we also picked a message queue, also stack-layer. Swapping it is a weekend of work for one engineer, because the surface area is small, no other decisions are anchored to it, and the contract is simple.
The layer tells you where a debt sits and who needs to be in the room, and reversibility tells you what the debt will cost, so if you stop at the layer you collapse these two into one category and get the wrong answer for both.
Run the four thumb rules
Once the layer is named, I run four checks on the candidate debt. I use four because they are independent: a debt can fail one and pass the others.
- Reversibility cost. Count the surfaces the debt touches and the people who would have to coordinate to change it. I also fold in hiring drag and the number of other decisions anchored to this one. The Angular call is high on all three: hundreds of surfaces, organisation-wide coordination to change, a narrower hiring pool than alternatives.
- Compounding vs decaying interest. Does each new feature deepen the lock-in, or does the debt stay flat? Compounding debts get worse on their own; decaying debts get cheaper to remove because the code around them is naturally rewritten. The framework call compounds with every new screen; a duplicated helper in a file scheduled to be rewritten next quarter decays.
- When it fails, what comes down with it. Does this debt take a single feature down, or does it pull unrelated systems with it? A debt that costs only "an engineer wastes an afternoon" is in a different category from one that costs "the product is down at scale."
- Time-to-visibility. Is the pain loud now or silent for two years? Loud debts get paid because someone is shouting, while silent debts (slowly worsening build times, slowly creeping coupling) accumulate until something snaps, which makes them the more dangerous of the two.
The Angular call fails all four. The queue choice fails maybe one, with limited downstream damage if it dies under load, so both sit at the same layer and only one is a debt to refuse.
Apply the four rules to decisions large enough to deserve them. Don't run a four-axis check on a duplicated helper function; the next section covers that.
Filter meaningful debt from benign
Most debt backlogs are half taste, so the filter is one question per item. Does it slow the next two or three features, force workarounds in unrelated code, raise incident risk or correctness risk, constrain hiring, sit on the path of next quarter's work, or compound? If yes, it stays. If the item reads as "could be more elegant" or "the files don't all follow the same pattern," drop it.
The cleanest illustration I have is the ideal-code engineer: a strong engineer with real taste who wants every file in the codebase written identically. Imposing that costs real things (engineer hours, review friction, conflict with the people who wrote the variants) and the benefit is invisible. Stylistic inconsistency in stable code is not debt. It is taste, and the leader should name it as taste in the conversation rather than letting the backlog grow without bound.
On the debt backlogs I have audited, cosmetic consistency is the dominant false positive, and rewriting code nobody is going to touch comes second.
When you take debt, name the exit
Speed-mode is the stretch where shipping fast is the only thing that matters: a sprint to first launch, a fire drill, a demo that decides a contract. When you are in it, you don't ask whether to take the debt. You ask when speed-mode ends. The exit is a date, a milestone, or a triggering event: first revenue, first hundred paying customers, a contract signed, a Series A close, six weeks from today.
On the first product I worked on, the initial sprint was correct: speed was the only thing that mattered. The miss was that we never defined the exit, and the drift that followed was stable for a reason: every release rewarded staying in speed-mode, the bill was always scheduled for later, and no single release ever looked like the obvious place to stop. We let the framework call, the architectural shape, and the foundational patterns calcify before anyone paused to ask whether we should.
If you cannot name the exit, you are not taking debt. You are letting the codebase drift and labelling it. The exit has to be specific enough that everyone on the team agrees it has been hit when it happens; "when we have time" and "next quarter" are not exits.
When you pay, pay what next quarter touches and what compounds
The debate about 20% per sprint versus dedicated sprints versus the boy-scout rule alone is a debate about how much, and the question it never settles is which debt to pay first.
I order the queue by two priorities. The first is the selective boy-scout rule: pay debt that sits on the path of upcoming features, because you are touching the code anyway and the marginal cost is low. The second is compounding debt: pay it even if next quarter does not touch it, because left alone it gets worse faster than you can outrun it.
Debt that decays gets deferred, because the code is going to be rewritten for product reasons inside a couple of quarters and paying it down now is work the rewrite throws away.
Most teams pay the loudest debt instead of the most compounding one, because the loud debt has an advocate in the room: someone is on the file, complaining, and about to rewrite it anyway, which is also why the loudest debts are usually decaying. The compounding ones have no advocate, since nobody is in that code yet, so the squeaky-wheel approach keeps feeling productive while paying down the wrong queue.
In the agent era, the leverage moved up the stack
Most of the per-file code in the codebases I touch now is machine output, so the bottom of the layer ladder is mostly automated. The structural decisions at the top (domain model, technology stack, architecture, foundational patterns) are not.
Agents leave compounding debt by default: they produce a strong v1 in two days, and four weeks later the application is bug-riddled in ways that are hard to localise, because the structural decisions (where the loop lives, what it iterates over, the data flow, the dependency boundaries) were never authored deliberately. Each new generation is plausible-looking code layered on a substrate nobody owns.
None of this makes the playbook matter less. It matters more, because the top three layers are now doing almost all of the long-term damage while the bottom layer is cheap to rewrite (the agent will rewrite it for you, whether or not you ask). Spend your attention on the structural decisions: which abstractions exist, where the boundaries sit, how data flows across them. Those are the hardest decisions to reverse, and you still need an experienced developer to make them well.
Tradeoffs and when not to use this
All seven are overkill on small calls: don't run the four thumb rules on a duplicated helper, and don't make the team name the layer for a one-day refactor. Match the discipline to the stakes, and give the most care to the decisions that are hardest to reverse.
All seven are dangerous to skip on the take side, even on a small team. The most common mistake I see is taking debt without naming the exit, so that speed-mode calcifies into the default, and the second most common is paying the loud debt instead of the compounding one. I have made the first of these myself, on the product this essay keeps returning to, which is why the exit gets a section of its own.