Technical Debt: What It Is and How to Pay It Down
What technical debt really costs, how to measure it in an inherited codebase, which debt to pay first, and when to modernize instead of patching.

On this page · 7 sections
- What is technical debt?
- What causes technical debt in a growing product?
- How do you tell harmless debt from debt that blocks the business?
- How do you measure technical debt in an inherited codebase?
- How do you prioritize which debt to pay first?
- When is technical debt a reason to modernize instead of patching?
- Want a second opinion on your codebase?
Technical debt is the future cost you accept when you ship code that is faster to write now but harder to change later. Like financial debt, it is not automatically bad. It becomes a problem when the interest grows faster than the product does. Here, the interest means the extra time every change takes.
I’m a Brazilian senior engineer who has inherited codebases and led a live migration. This guide focuses on what matters to founders and CTOs: which debt to ignore, which to pay first, and when patching stops making sense.
Key takeaways
- Debt is a cost on future changes, not a measure of ugly code. Code nobody touches costs almost nothing.
- The dangerous debt sits on the money paths (signup, checkout, billing) and on the build and deploy process.
- You can measure debt without special tools: time to ship a small change, incident history, and areas nobody dares to touch.
- Prioritize by interest paid, not by how much it offends you. Pay debt where you are about to work anyway.
- Modernize instead of patching when the debt is in the foundation and every feature has to fight it.
What is technical debt?
The metaphor is usually credited to Ward Cunningham, who used it in the early 1990s. He used it to explain why shipping a first, imperfect version of the code can be a reasonable choice, as long as you go back and fix what you learned.
In practice, technical debt shows up as a handful of concrete things:
- Shortcuts taken to hit a deadline and never revisited.
- Outdated frameworks, libraries or runtimes that no longer get security updates.
- Missing tests, so nobody is sure a change is safe.
- Missing documentation, so knowledge lives in one person’s head.
- Design decisions that made sense for ten users and hurt at ten thousand.
Notice what is not on the list: code you simply don’t like. Style preferences are not debt. Debt always has a price you can describe in terms of time, risk or money.
What causes technical debt in a growing product?
Most debt is not the result of bad engineers. It comes from reasonable decisions under pressure, and from the product changing underneath the code.
Deliberate shortcuts
A team skips tests or hardcodes a rule to meet a launch date. This is the healthy kind of debt if someone writes it down and plans to come back. It turns toxic when it is forgotten.
Changing requirements
The product pivots, but the data model and names still describe the old business. Every new feature has to translate between the old idea and the new one.
Turnover and handoffs
When an agency or a developer leaves, the reasons behind decisions leave with them. The next person patches around what they don’t understand, which adds a layer of debt on top.
Neglected upgrades
Dependencies age. Skipping minor upgrades for two years turns a small task into a risky project, and the interest compounds quietly.
Speed without guardrails
Fast prototyping, including AI-generated code, is fine for learning. Debt accumulates when the prototype becomes the product without tests, access control or a review step.
How do you tell harmless debt from debt that blocks the business?
Ask one question: what happens if we leave this alone for another six months? If the answer is nothing, it’s harmless. If the answer is a slower roadmap, an outage or a security exposure, it’s blocking.
| Harmless debt | Blocking debt |
|---|---|
| Messy code in a module nobody changes | Messy code in the checkout or billing flow |
| Inconsistent naming in an internal tool | No way to deploy without one specific person |
| Old library with no known vulnerabilities, still maintained | Runtime or framework past end of support |
| Missing tests on stable, rarely used code | Missing tests on code that changes every week |
| Duplicated code in a low-risk area | Business rules duplicated in several places that drift apart |
The pattern is that debt becomes dangerous when it overlaps with change, money or security. Debt in a quiet corner is background noise. Debt in a busy corridor is a hazard.
How do you measure technical debt in an inherited codebase?
You rarely get a clean number, and I’d be suspicious of any tool that claims to give you one. What you can get is a set of signals that, together, tell you how expensive the code is to live with. When I take over a codebase, I work in this order: access first, then the build, then the money paths, then the risks. Debt measurement fits into the same sequence.
Signals worth collecting
- Time to ship a small change. This is usually the first thing I time. Pick a trivial task, like changing a label or adding a field, and time it from ticket to production. Long times point to build, test or deploy debt.
- Can you build and run it from scratch? If a fresh machine can’t run the project from the README, there’s hidden knowledge.
- Incident and bug history. Which files or features show up repeatedly? Repeat offenders are where interest is highest.
- Change hotspots. Version control history shows which files change most. Files that change often and break often are your top candidates.
- Fear map. Ask the team which parts they avoid touching. This is subjective but surprisingly accurate.
- Dependency and runtime status. List what is out of support or has known vulnerabilities.
- Test coverage where it matters. Overall percentage is less useful than whether the money paths have any tests at all.
Write the findings in a short document with a rough cost for each item, such as “slows every release,” “risks an outage” or “blocks a feature.” Estimates are fine as long as they’re labeled as such. A deeper review is a separate code audit. I’d treat that as a next step if these signals look bad.
How do you prioritize which debt to pay first?
Think in terms of interest and principal. Interest is what the debt costs you each week. Principal is the effort to remove it. Start with high interest and low-to-moderate principal.
- Security and data-loss risks. Exposed secrets, missing access control, no backups. These come first regardless of effort.
- Money paths. Anything touching payments, signup or the flow that earns revenue. A bug here has a direct price. Having worked on the cart and checkout team at Total Wine & More, I always look at checkout code with extra care.
- Delivery pipeline. If deploys are manual and scary, every other fix is slower and riskier. Fixing this early lowers the cost of everything after. Releasing through a canary deployment to a few users first makes deploys far less scary.
- Hotspots. Files that change often and break often.
- The rest. Leave it until you have a reason to touch it.
A rule of thumb I use, and it’s my own heuristic rather than a standard: pay debt in the area where you are about to build a feature. You get the benefit immediately, the scope stays small, and the business sees the link between cleanup and delivery. Reserving a steady slice of each sprint tends to work better than a single heroic cleanup. The big cleanup usually gets cancelled when a deadline appears.
At Inovaula, I built v2 of a web platform and then led the team, and we reached the steadiest delivery flow that team had had. My personal takeaway from that period, not a measured result, is that cleanup works best as a normal part of the work. It doesn’t need to be a separate project that requires permission.
When is technical debt a reason to modernize instead of patching?
Patching works when debt is local: a messy module, an old library, a missing test suite. Modernization is worth considering when the debt is in the foundation and every feature has to fight it.
Signs you’ve crossed that line:
- The framework or runtime is unsupported and can’t be upgraded step by step.
- Hiring is hard because few engineers know the stack.
- Each fix creates a new bug elsewhere because the pieces are tightly tangled.
- The architecture can’t support something the business needs, such as multiple customers, real-time features or scale.
- The cost of patching, added up over a year, exceeds the cost of moving.
Even then, I prefer an incremental migration over a big-bang rewrite. A rewrite usually costs more and takes longer, because the old system has to keep running in parallel while the new one catches up. At avanzzada I led the full migration of a platform that applies to job openings automatically for its users. We moved its whole architecture from a legacy stack to a new one while people kept using it every day. The rule I follow for this kind of move is to put a switch in front of everything before writing new code. Then you move one piece at a time.
Patch the small stuff, modernize the foundation, and don’t confuse the two.
Want a second opinion on your codebase?
If you’ve inherited a codebase and aren’t sure which debt matters, email me@filipeeduardo.dev. Include a short description of the stack and what hurts, and I’ll tell you what I’d check first.
Frequently asked questions
Is all technical debt bad?
No. Taking on debt deliberately to test an idea or hit a launch can be the right call. It’s bad when it’s invisible, unplanned or sitting on critical paths.
Can you eliminate technical debt completely?
No, and trying wastes money. Software changes, so some debt always accumulates. The goal is to keep the interest low enough that the team ships at a steady pace.
How much time should a team spend on technical debt?
There’s no universal percentage. A practical approach is to reserve a consistent portion of each sprint and spend it on the highest-interest items. Then adjust based on how fast changes ship.
Is refactoring the same as paying down technical debt?
Refactoring is one way to pay it down: improving the structure without changing behavior. Debt can also be paid by upgrading dependencies, adding tests, fixing the deploy process or writing documentation.
Should I rewrite from scratch if the codebase is a mess?
Rarely as a first move. Start by measuring where the cost really is. Often a few hotspots and a weak deploy process cause most of the pain, and these can be fixed without a rewrite.

