Technical Debt Is Allowed
One day the architects say "technical debt is allowed," and whoever really hears it already knows what's coming. What counts as debt and what doesn't, Fowler's quadrant, how healthy debt migrates quadrants on its own, and a minimal structure for recording it in two minutes.
One day, in a meeting, the architects say: "technical debt is allowed."
Whoever really hears that facepalms right there, because they already know what's coming.
#Whoever runs after the ball
Some people run after the ball. Low judgment, no questions asked, doing just to be doing.
You start seeing naming problems. Architecture that shifts with the mindset and mood of whoever coded that day. Coupled intentions where there shouldn't be any relationship at all. God tables and God entities that do a little bit of everything. A separation of responsibilities so anemic it barely registers anymore. Everyone running, because "there's no time."
#The day nobody takes ownership
The day arrives. And nobody takes ownership of anything.
Nobody tracked the technical debt. Most of it ended up buried in the code, and by now nobody remembers where to even pick up the thread to start paying it down. The team's leads change, and all that's left is the same old line: "this runs on a miracle."
#"Runs on a miracle" is an insult
Saying something "runs on a miracle" sounds harmless. It isn't.
If something runs, it's not a miracle — it's that at some point someone actually thought through the design behind it. Things work according to what they were built for. Calling it a "miracle" erases that work, and along the way it disrespects the developer who did take the time to think it through, while handing the same label to whoever didn't think at all and just got lucky.
#There's no good or bad debt — there's intent
Before going further it's worth pinning down what we're talking about, because "technical debt" gets used for everything.
Technical debt is any decision made to move faster today that creates an extra cost — in time, risk, or effort — to change the system tomorrow. The point isn't "ugly code": it's the trade-off between speed and structural quality, whether you make it consciously or not.
So the question isn't whether technical debt is acceptable in the abstract. It's whether you took it on with intent, with context, in response to a real business need — or whether it just showed up because nobody had time to think.
Martin Fowler laid this out along two axes, and it's the framing I get the most out of:
| Deliberate | Inadvertent | |
|---|---|---|
| Prudent | "We know this doesn't scale, but we need to ship now" | "Now that we've built it, we can see how it should have been done" |
| Reckless | "We don't have time for tests" | The team didn't know the good practices |
The only quadrant you actually choose is prudent + deliberate: you know what you're leaving behind and you know you'll pay for it. Prudent + inadvertent isn't chosen, it's learned — the right design only becomes visible after you've built the thing, and at that point the only sensible move is to write it down.
The two reckless ones are what turn into a trap. And the worse of them isn't the deliberate one, it's the bottom right: if nobody decided anything, there's nothing to record and nobody who'll remember why later.
#Nothing is as permanent as a temporary fix
There's something the quadrant, drawn like that, doesn't show: it isn't a snapshot. It's where the debt stood the moment you took it on, and that position moves.
Prudent, deliberate debt turns into reckless, deliberate debt at a very precise moment: when the team already knows it's there and, knowing it, keeps building on top without putting a date on it. That's when it stops being "ship now, fix it later" and becomes "we don't have time to do it right" — which is, word for word, the reckless quadrant.
What drives that migration is that everything new you rest on top raises the price of paying it off. The temporary decision turns structural: first one module depends on it, then three, then the whole design assumes things work that way. The stopgap became the standard, and you're no longer paying off a shortcut — you're rebuilding the system.
That's why recording it isn't bureaucracy. Prudent debt with no date and no owner doesn't sit still waiting to be paid off: it changes quadrant on its own.
#What counts and what doesn't
A good share of the arguments about technical debt are really arguments about what counts as debt. This is what I use to cut them short.
This is debt:
- Duplicated code you should have abstracted.
- Missing tests on critical logic.
- Architecture that doesn't support a use case you already know is coming — a monolith when you already know you'll have to scale one module separately.
- Outdated libraries or frameworks that block future upgrades.
- Missing documentation, which turns every change into archaeology.
- Design decisions made to get by — business logic in the controller instead of the domain.
- Badly decoupled dependencies: that hexagonal violation you let slide.
This isn't debt, even when it gets called that:
- Bugs. A bug is a defect, not a decision. Debt is about design, not functional errors.
- Missing features. That's the product backlog.
- Old code that works fine and blocks nothing. If changing it costs you nothing, it isn't debt — it's old code, and it's fine that it exists.
- "I don't like how this is written." Taste isn't debt unless it creates real, measurable friction.
And when you're unsure, the quick test: is this costing me time or risk right now, every time I touch code near it? Or is it a theoretical concern?
If it's theoretical, it isn't debt: it's an architectural opinion. It might be a very good opinion, but it gets prioritized differently — much further down the list.
#The cognitive shortcut: merging what shouldn't be merged
Under pressure, the shortcut is almost never "let's skip the test." It's subtler: reusing and merging things that have different intentions and different use cases, because in the short term it feels like moving faster.
// One entity doing persistence, API contract, and event payload all at once
public class Order
{
public Guid Id { get; set; }
public string CustomerEmail { get; set; } // needed for the event, not for persistence
public decimal InternalDiscount { get; set; } // should never leave the API
public string Status { get; set; }
}
Order works for everything, until the day the event needs a field the table doesn't have, or the API exposes a field that should never have left the building. Every reuse plants an assumption that breaks the moment just one of those three use cases changes on its own.
And yes: the previous section listed duplicated code as debt, and here I'm saying the opposite. It isn't a contradiction — they're the same failure seen from either side. Duplicating what is really one thing and merging what is really three are both the result of never asking how many distinct intentions are actually in play.
#Debt starts charging interest
Technical debt doesn't explode all at once. It's a nickel-and-dime expense: it piles up little by little, nobody keeps a record, and you don't feel it until the day you have to touch the code next to it.
This happened once to me and whichever team I was on at the time.
We took on a debt in February, with a payment date set for sometime after May — May being when the production deployment went out. It sounded reasonable: ship now, and once the system is live we'll have room to come back and do it properly.
It never got documented, and nobody followed up on it. The room we all assumed we'd have never materialized, because nobody accounted for something fairly obvious: what follows a deployment isn't calm, it's change requests.
The line going around was "let the maintenance team deal with it." Nobody accounted for the other half of that sentence: that the maintenance team might well turn out to be us.
That's where it starts to actually hurt. Every new change leaned on the debt already sitting there, and building on debt generates more debt. The debt started charging interest.
The one happy part of the story is the management layer, because the screen matches the Word doc. Underneath, every change that doesn't get fixed at the root costs more in resolution time. What could have taken a day starts getting estimated at three. Then at a week.
And once the estimates stretch far enough, the comment arrives:
"So why didn't we just do it this way from the start?"
Guess who ends up wearing it. It's the same dynamic I wrote about in It's Like They Never Heard Me Say It: with no record, the version that sticks is whoever's telling it afterward.
And here's the part that stayed with me most. That debt had actually started out fine: it had a date. It was prudent and deliberate, the healthy quadrant. But the date lived in a couple of people's heads, not in a document with an owner — and a date that isn't written down isn't a commitment, it's an intention.
By the time the interest is visible, nobody remembers this all came out of a decision to move fast. The debt stayed; the decision behind it didn't. And the person who ends up explaining it is the one paying it off.
This is why ADRs matter so much here, and why when you write one matters most of all. An ADR isn't written post-mortem, once you already have to explain why the system ended up like this. It's written while you're designing the solution — the only moment when there's still nothing to justify.
Written then, it stops being documentation and becomes backing: proof that the shortcut was a decision made and approved, not an oversight by whoever implemented it. When the "why didn't we do it this way from the start?" arrives and nobody wants to own having approved the other thing, that document is the only thing left standing.
#Register it fast, not perfectly
The reason debt almost never gets tracked isn't "I didn't want to document it" — it's that documenting it feels like losing time you don't already have. That's why the record has to be fast, not polished.
You don't need a process. Anything auditable is enough:
- A line in the team's DevOps wiki, on a page dedicated to technical debt: what got left pending, why, and where it lives in the code.
- A short email, cc'ing whoever's relevant (TL, architects), stating what debt is being taken on and why. Same principle as an ADR: you don't need to convince anyone in the moment, it's enough that it has a date and named recipients.
- A one-line ticket in the backlog, tagged as technical debt.
What does help is having a fixed structure, so you're not starting from a blank page every time. This is the smallest one I use — plain text on purpose, so it drops just as easily into a notepad, a wiki, Notion, or a ticket description:
# TD-014 — Duplicated coupon validation in checkout
Date: 2026-08-19
Taken by: (you) — agreed with: (TL, architecture)
Quadrant: prudent + deliberate
What we did: the validation ended up duplicated in the controller instead of moving to the domain.
Why: the release window didn't allow touching the three modules that depend on it.
Where it lives: CheckoutController — coupon validation.
What it costs today: every change to the coupon rules has to be made in two places.
When it gets paid: the sprint after the release. Owner: (name).
It fills in in two minutes, and every field is there for a reason that already came up in this post:
- Where it lives is what stops anyone six months from now from having no idea where to pick up the thread.
- When it gets paid and owner are what hold the debt in the prudent quadrant. Without them, it migrates on its own.
- What it costs today is what lets you prioritize it against the rest of the backlog instead of arguing about it on taste.
- Taken by / agreed with is the part that acts as backing: it puts in writing that this was a decision, not an oversight.
And note this isn't the same thing as an ADR. The ADR records the design decision; this records the shortcut you took relative to that decision, and when you plan to come back. One ADR per architectural decision; one debt record every time you knowingly cut a corner — which is quite a bit more often.
The format matters less than the properties it has to meet: a date, an owner, an explicit scope — how far it reaches and how far it doesn't — and a place nobody can quietly edit later. Debt that exists only in the team's memory isn't tracked: it's waiting for the next round of turnover to disappear.
#If you're the one moderating
Where this framing really earns its keep is the retro or backlog refinement.
When someone says "that's technical debt," ask them two things: which quadrant it falls in, and what concrete cost it creates today. Not as a quiz — as a way to turn the conversation from a list of complaints into a list you can prioritize.
In my experience those two questions filter out more than half of what gets called "debt" and is really aesthetic preference. And what survives the filter is exactly what's worth recording and paying down.
#The balance
Technical debt isn't bad by itself. It's a tool — like financial leverage — when it's chosen with intent and put on record. It's a trap when it's the default answer to "there's no time."
The facepalm from whoever hears "technical debt is allowed" isn't about the debt. It's because they already know nobody's going to track it.
#technical-debt #architecture #team-practices #refactoring