Insight

Most Technical Debt Is Deferred Judgment, Not Bad Code

The expensive part of technical debt is usually not ugly code. It is delayed judgment about boundaries, states, and what the system is actually allowed to become.

Alaa Almallah 9 min read

People talk about technical debt as if it begins when the code becomes embarrassing.

Usually it begins earlier: the moment a team knows a decision matters and keeps moving without deciding it clearly enough. A boundary stays fuzzy. A workflow is allowed to have three competing truths. A service owns something in practice that nobody says it owns in theory. The code is only the visible residue of that delay.

That is why so much "debt reduction" feels cosmetically active and structurally weak. Teams rename, refactor, extract helpers, and introduce standards, but the burden remains because the judgment underneath still has not been made.

Bad code can be expensive. Deferred judgment is worse. Bad code is often local. Deferred judgment spreads.

Debt is often a symptom of unresolved ownership

The important question is not "where is the messy code?"

It is:

  • what did we postpone understanding?
  • what relationship in the system is still undefined?
  • what responsibility is being shared ambiguously between layers?

That is the real debt field.

If a billing rule lives partly in the UI, partly in the API, and partly in support habit, the problem is not code quality first. The problem is that the business rule was never given one authoritative home.

Once that happens, every cleanup pass becomes a partial ritual.

Code rot and judgment rot are not the same thing

This distinction matters:

ConditionWhat it looks likeWhat actually fixes it
Code rotduplication, poor naming, fragile tests, weak cohesionlocal refactoring and better standards
Judgment rotunclear boundaries, unresolved ownership, conflicting state modelsarchitectural decisions with explicit consequences
Mixed caseboth at oncedecide first, refactor second

Many teams reverse the order.

They refactor first because refactoring feels industrious and politically safer than saying, "We still do not understand this system well enough."

But code cannot become clean around a relationship the team is still refusing to name.

The debt was often created in language before it appeared in code

There is usually a sentence people keep saying when the debt is forming:

  • "We will clean it later."
  • "This probably belongs here for now."
  • "Let us keep it flexible."
  • "We can decide ownership once usage grows."

Those sentences are not harmless. They are design events.

A system is already being shaped by them.

If the team keeps shipping around that linguistic fog, the codebase starts to mirror it. Different modules encode different guesses about the same thing. Review gets slower because reviewers are not only checking implementation. They are re-arguing unresolved system structure every time a change arrives.

That is why some debt feels like constant drag even after many cleanups. It is not merely old code. It is active indecision.

The highest-interest debt is state confusion

The worst debt is not usually syntax or duplication. It is when the system has no clean answer to:

  • what state is this object actually in?
  • who is allowed to move it to another state?
  • what must be true before that move happens?
  • where is that truth enforced?

If those answers are split across too many places, the debt compounds quickly.

This is one reason small systems become hard before they become large. The issue is not scale yet. The issue is that the state model was allowed to stay socially understood instead of technically declared.

See Building Systems That Survive Change for the structural version of this problem. That post is about freeze lines and stable cores. This one is about the earlier mistake: not deciding enough to know where those lines belong. The next step after this piece is usually Good Architecture Tracks Decisions Across Unfolding States, because delayed judgment eventually becomes missing system memory.

Debt registers are useful, but they do not replace understanding

A backlog ticket saying "untangle service boundaries" is not understanding.

A debt spreadsheet saying "consolidate logic" is not understanding.

These tools help only if they are downstream of a clearer system model.

Otherwise they become moral bookkeeping. The team feels responsible because the debt was recorded somewhere, but the record does not reduce ambiguity. It only preserves awareness of it.

That is not nothing. It is also not enough.

What real debt payment looks like

Paying down meaningful debt usually means one of four harder moves:

  1. Choose one place where a rule becomes authoritative.
  2. Remove one ambiguous pathway between states.
  3. Name one boundary that used to exist only socially.
  4. Delete one abstraction that was covering indecision instead of resolving it.

Notice that only one of these is directly about code form. The others are about system truth.

That is why senior engineering work often feels less like cleanup and more like clarification.

Why AI can make this worse faster

AI coding assistants can accelerate local production while amplifying deferred judgment.

If the structure is still blurry, the assistant will often produce more code around the blur:

  • one more wrapper
  • one more helper
  • one more variant of the same rule
  • one more service boundary that feels reasonable in isolation

The problem is not that the model is careless. The problem is that it is obedient to unclear reality.

That is why Working with AI Coding Assistants argues for a repo constitution. The repo constitution is one answer to deferred judgment: it names what the system is before generation accelerates what it is not.

A better question than "how much debt do we have?"

Ask this instead:

Where is the system still asking humans to remember what it should have decided structurally?

That is usually where the interest rate lives.

If support remembers special cases the system cannot express, debt is there. If reviewers keep re-explaining the same boundary in PR comments, debt is there. If a production incident reveals two teams had different assumptions about one transition, debt was there long before the incident.

Practical review prompts

Use these in architecture review or retro:

  • Which rule exists in more than one place because we never chose one owner?
  • Which state transition is still culturally understood rather than structurally enforced?
  • Which abstraction was added to avoid deciding something more uncomfortable underneath?
  • Which recurring bug is really a conflict between two models of the same object?

Those questions are better debt detectors than a lint pass.

The sharper frame

Technical debt becomes expensive when the system keeps carrying unmade decisions forward in time.

That is the real cost.

Not ugliness. Not shame. Not even complexity by itself.

The cost is that future work has to negotiate with a past that never made itself explicit.

Good engineering does not only make code cleaner. It makes the system less dependent on memory, inference, and repeated human reinterpretation.

The highest form of debt reduction is not tidying. It is finally deciding.

If your system feels slower than its code quality should justify, the problem may be deferred judgment rather than engineering effort. If you want help naming those structural decisions before more work piles on top of them, book a discovery call.

Related