· By John Kavanagh

Technical Debt vs. Bad Code

Abstract image used to represent Technical Debt vs. Bad Code
Image by Oskars Sylwan, licensed from Unsplash.

Calling a piece of code "bad" tells me that somebody is unhappy with it. Calling it "technical debt" ought to tell me something more: what extra work does its current design make necessary, and when are we likely to pay for that work?

Technical debt is a metaphor for the future cost imposed by shortcomings in a system's internal design or implementation. "Bad code" is a much broader judgement. The two can overlap, but neither the age of the code nor somebody's dislike of its style establishes a useful debt claim.


Where the Metaphor Came from

In his 1992 report on the WyCash system, Ward Cunningham described the need to consolidate an implementation as it developed. Code could work acceptably for the customer yet become increasingly difficult for the team to understand and change. He compared shipping an immature implementation with taking on debt, and the continuing effort of working around it with interest.

The metaphor connects the implementation we have with the cost of continuing to develop it. A design may have been reasonable when written but no longer reflect what the team has learnt.


Debt isn't Always a Deliberate Shortcut

One definition reserves technical debt for a conscious decision to defer better design in exchange for an earlier release.

It isn't the only established interpretation. Martin Fowler's technical debt quadrant, published in 2009, distinguishes deliberate from inadvertent debt and prudent from reckless decisions. His account includes teams discovering a better design after doing useful work, rather than knowingly taking a shortcut.

I find that broader interpretation helpful. We still have to deal with the extra work, whether it came from deadline pressure, an overlooked dependency, or something we've learnt about the domain. Knowing how it happened helps, but doesn't make the work go away.


Make the Cost Visible

If a content platform implements the same publishing rule separately in the editor, preview, and delivery application, all three implementations might be perfectly readable. Changing the rule still means finding each version, checking that they agree, and coordinating their release. That extra work may be the debt.

An awkwardly named helper with one caller, a clear test, and no expected changes is a different proposition. I'd be happy to rename it when working nearby, but I wouldn't put it ahead of the duplicated publishing rule simply because I disliked the name.

A debt ticket should name the extra work: duplicated changes, a dependency that obstructs upgrades, or a constraint people keep having to rediscover. Then it can propose a specific improvement. For the publishing rule, that could mean agreeing one authoritative definition and adding checks at the integration boundaries, without replacing the whole platform.


Interest is an Analogy, Not an Invoice

Fowler's later explanation of technical debt connects the metaphor to the additional effort of changing a system. It also acknowledges how uncertain the estimates are. We can judge that a design makes work harder without pretending to know its interest rate to two decimal places.

I'd rather see what the last few changes cost than a grand total assembled from guesses. If three changes each required manual reconciliation across several implementations, there's something concrete to discuss when planning the next one.


Bad Behaviour Needs Its Own Explanation

A calculation which produces the wrong answer is a correctness problem. A control which excludes keyboard users is an accessibility problem. Calling either "debt" shouldn't obscure the people affected or turn a current defect into an optional future tidying exercise.

Conversely, code can produce the right result and still make every subsequent change unnecessarily difficult. Passing tests doesn't settle the internal design question, just as attractive formatting doesn't prove that a design will be cheap to maintain.

The remedy depends on the problem. Refactoring, replacing a narrow implementation, removing an unused feature, and accepting a limited cost are all possible decisions. The rewrite, refactor, or strangle decision guide explores those choices in more detail.


Untangling a delivery problem?

Send the symptoms, constraints, and affected routes. I'll help identify whether the issue sits in the application, platform, content model, deployment path, or search surface.