Every client who comes to us with a “quick fix” request eventually asks the same question: why does something so small cost so much? Usually the answer is technical debt — the accumulated shortcuts, outdated dependencies, and “we’ll fix it later” decisions that pile up in a codebase over time.
Debt isn’t a moral failing
Technical debt isn’t a moral failing. Sometimes it’s the right call — you ship the MVP with a hacky admin panel because the business needed to launch in six weeks, not six months. The problem isn’t taking on debt, it’s never paying it down. I’ve inherited codebases where a single PHP file handled routing, database queries, and HTML output for twenty different page types, because that’s how it started three years earlier and nobody had time to refactor.
What technical debt actually costs, concretely
First, feature velocity drops. A change that should take two hours takes two days because the developer has to trace through tangled dependencies to figure out what will break. Second, bug rates climb — tightly coupled code means a fix in one place creates a regression somewhere unrelated. Third, and this is the one clients feel most: onboarding new developers becomes painful. If a new hire needs three weeks just to understand how the checkout flow works before they can touch it safely, that’s debt showing up as a hiring cost.
The warning signs are fairly consistent across projects
If your team dreads touching a particular part of the app, that’s debt. If every deploy requires “the person who understands that module” to babysit it, that’s debt. If your dependency list includes packages that haven’t been updated in three or four major versions, that’s debt with a security dimension attached, not just a maintainability one.
A real example
I worked on a project where the client’s WooCommerce site had been customized so heavily with one-off plugins and inline function overrides that a routine WordPress core update broke checkout for four hours during a sales event. The fix itself took twenty minutes. Finding it took most of the day, because nobody — including the client’s previous developer — had documented what the custom code actually touched. That’s the real cost: not the twenty-minute fix, but the six hours of forensic work to find what to fix.
When do you actually address it?
I tell clients to treat technical debt like financial debt — some of it is fine to carry if it’s serving a purpose, but you need to know your interest rate. A few practical triggers: if a feature request keeps getting quoted higher than it should reasonably cost, debt is probably the reason, and it’s worth a focused sprint to address the underlying architecture rather than layering another patch on top. If you’re about to scale — more traffic, more developers, more integrations — that’s the moment to pay down debt before it compounds, not after. And if a core dependency is approaching end-of-life, that’s not optional; it needs to be scheduled.
Rewrite vs. refactor
The mistake I see most often is treating a rewrite as the only solution. Full rewrites are expensive, risky, and frequently underestimated — I’ve seen a “3-month rewrite” turn into 14 months because nobody accounted for feature parity edge cases the old system quietly handled. Incremental refactoring is almost always the better path: isolate the worst offender, write tests around its current behavior so you know if you break something, then replace it piece by piece. It’s less satisfying than a clean rewrite, but it keeps the business running while you fix the foundation.
The bottom line
If you’re evaluating whether your own site or application has a debt problem, ask your development team a blunt question: “If I asked for this feature, how confident are you in the estimate, and why?” Vague answers or wildly padded estimates are usually debt talking. A good developer will tell you directly if the codebase is fighting them, and a good agency will scope a debt-reduction pass as its own line item rather than quietly burying it inside every future quote.