Engineering
The Real Cost of Technical Debt (It's Not What You Think)
Technical debt is not a number you can measure. It is a tax on every future decision, and it compounds silently until the cost of change exceeds the value of changing.
The term "technical debt" has become so overused that it has lost most of its meaning. Developers use it to justify rewrites. Managers use it to explain missed deadlines. Founders hear it as a vague complaint from the engineering team.
Let me offer a more precise framing: technical debt is not a property of your code. It is a property of your decision-making velocity. A codebase with high technical debt is one where every decision requires understanding and working around an increasing number of prior decisions. The cost is not storage or compute. The cost is cognitive overhead, compounded over every engineer who touches the system.
The two types of debt worth distinguishing
Not all technical debt is equal. The Ward Cunningham original formulation is useful here: he described debt as a deliberate decision to take a shortcut now and pay it back later, with interest. The key word is deliberate.
Intentional debt is a known trade-off. We build the simpler architecture now because speed-to-market matters more than extensibility at this stage. This is legitimate engineering judgement.
Accidental debt is what accumulates when shortcuts are taken without explicit acknowledgement. Code that was "good enough for now" but whose limitations were never documented. Assumptions that made sense in week one but are now embedded in six different call sites. This is the expensive kind.
The distinction matters because intentional debt has a known cost and a payment plan. Accidental debt has neither.
How debt actually compounds
Here is the mechanism that teams consistently underestimate.
A new feature requires modifying a part of the system that has accumulated debt. The engineer working on it now has to understand the existing code before they can change it. This takes longer than estimated. To meet a deadline, they make their change in a way that works but adds to the existing debt layer. They do not document it because there is not time.
Six months later, another engineer repeats this cycle. The codebase is now more coupled, less legible, and more expensive to change. The second engineer has less context than the first because the first engineer has moved to another project.
This is not a failure of discipline. It is a predictable outcome of a system that treats speed as the only variable. The interest rate on unacknowledged technical debt is not linear - it accelerates as the codebase grows and team composition changes.
What we look for when inheriting a codebase
When HeapiFy takes over an existing project, we do a structured assessment before touching a line of code. The indicators that predict high carrying costs:
Lack of boundary enforcement. If database access is spread across view components, or business logic lives in API route handlers, every change carries risk far beyond its apparent scope.
Implicit assumptions in shared state. Global state that is mutated by multiple subsystems, without clear ownership rules, is a source of heisenbugs that are expensive to diagnose.
Test coverage in name only. A high test count with low scenario coverage is often worse than honest low coverage - it provides false confidence. We look at what the tests actually exercise, not the percentage.
Outdated dependencies. This is often a leading indicator of neglect. A codebase with dependencies four major versions behind has typically not been maintained proactively in other dimensions either.
The cost of doing nothing
The most dangerous position on technical debt is "we'll address it when we have time." In our experience, the time never arrives - not because teams are irresponsible, but because the pressure that created the debt does not abate on its own.
The practical consequence: codebases that are not actively maintained tend to cross a threshold somewhere between 18 and 36 months where the cost of adding new features exceeds the cost of rewriting. At that point, the debt is no longer a tax on velocity - it is a ceiling on what is possible.
What responsible debt management looks like
Make it explicit. Every shortcut should be documented with a note explaining the constraint, the trade-off, and what would need to change to fix it. This is not extra work - it is the cost of making the shortcut intentional rather than accidental.
Budget paydown work. Allocate a fixed percentage of each sprint to paydown. In our projects, we typically recommend 15–20%. This is not overhead. It is maintenance of your primary engineering asset.
Prefer seams over patches. When you cannot fix something completely, build a clear interface around it. Isolate the debt so it cannot propagate. A well-wrapped bad decision is significantly less expensive than a bad decision spread across a codebase.
Measure what matters. Not lines changed or story points - time to first commit for a new engineer, time to implement a new feature in an area of the codebase that has not been touched recently. These are the actual metrics of technical debt.
The goal is not a perfect codebase. The goal is a codebase where the cost of change is predictable and does not trend upward.
Stay Current
Engineering notes, when we have something worth saying.
No weekly newsletters. No content marketing. When a post goes up, you'll get it.