# Technical debt: how to measure it and pay it down

> Technical debt is the extra cost of changing code built on shortcuts or outdated design. How to measure it, track it in a register and pay it down.

- URL: https://computese.com/technical-debt/
- Author: Duong Quan Nguyen, CEO, Computese
- Published: 2026-10-09
- Updated: 2026-10-09
- Topics: Custom software

## In short
- Technical debt is the extra cost each change carries because of shortcuts or outdated design. Fixing it is the principal; slower work, defects and risk are the interest, and you pay that interest only where the code changes.
- Some debt is the right call, such as a prototype shipped to learn. Ward Cunningham, who coined the metaphor, wrote in 1992 that a little debt speeds development so long as it is paid back promptly.
- No single metric measures it: Google explored 117 candidate metrics and no single one predicted what engineers reported. Combine DORA delivery metrics, hotspots from version control, dependency age and end-of-life dates, static analysis and a short survey.
- Keep a register with each item's symptom, interest, cost to fix, owner and trigger. Fix small debt in the code you are already changing, reserve capacity for larger items, and replace big systems piece by piece instead of rewriting them.
- Prevent the year-two pile-up: upgrade runtimes before end of life (Node.js 20 ended on 30 April 2026; .NET 8 and 9 end on 10 November 2026), gate new code, and give every deliberate shortcut an expiry date.

Technical debt is the extra cost a team pays on every change because code, design or tooling is not in the shape the system needs. Fixing it is the principal; the slower work, defects and risk it causes until then are the interest. Some debt is worth taking. Unrecorded debt in code that changes often is what stalls teams.

This guide is for the manager or owner asked to fund "time for tech debt" who wants to judge the request on evidence. Versions and dates are as published on 9 October 2026.

## What is technical debt, and where does the term come from?

Ward Cunningham coined the metaphor to explain the refactoring his team was doing on WyCash, a portfolio management system written in Smalltalk. His [OOPSLA '92 experience report](https://c2.com/doc/oopsla92.html) says: "Shipping first time code is like going into debt. A little debt speeds development so long as it is paid back promptly with a rewrite." The danger is debt that is not repaid: "Every minute spent on not-quite-right code counts as interest on that debt."

In a later [video, transcribed on the C2 wiki](https://wiki.c2.com/?WardExplainsDebtMetaphor), he argued that many bloggers had confused the metaphor with writing code poorly and fixing it later: "I'm never in favor of writing code poorly, but I am in favor of writing code to reflect your current understanding of a problem even if that understanding is partial." When the code falls behind what the team has learned, the team keeps stumbling over the disagreement, and that slowdown is the interest.

Martin Fowler's [explanation](https://martinfowler.com/bliki/TechnicalDebt.html) makes the arithmetic concrete. He calls the problem cruft: "deficiencies in internal quality that make it harder than it would ideally be to modify and extend the system further." In his example, cruft turns a four-day feature into a six-day one: two days of interest. Removing it would take five days, the principal. That loses on one feature (nine days instead of six) but wins once two more similar features are coming. And you pay interest only when you work on that part of the code, so "crufty but stable areas of code can be left alone."

![Isometric drawing: parcels travel from a laptop to a server over a tangled grey module, each dropping a cube into a bin whose heap is orange. A second tangled module off the route has an empty bin.](https://computese.com/images/blog/technical-debt/interest-pile.725aff0ed0-1536.webp)

*Every change that crosses the cruft pays interest; the same cruft where nobody works costs nothing.*

Researchers later formalized the idea. At [Dagstuhl Seminar 16162](https://drops.dagstuhl.de/storage/04dagstuhl-reports/volume06/issue04/16162/DagRep.6.4.110/DagRep.6.4.110.pdf) in April 2016, attendees defined technical debt as "a collection of design or implementation constructs that are expedient in the short term, but set up a technical context that can make future changes more costly or impossible," with an impact "limited to internal system qualities, primarily maintainability and evolvability."

## Is technical debt the same as a bug?

No, and treating the two as one skews priorities. A bug is behaviour that users, tests or monitoring can see. Debt is internal: the Dagstuhl definition limits its impact to internal qualities, so a system can behave correctly today and still cost more to change every month.

|                   | Bug                                         | Technical debt                                             |
| ----------------- | ------------------------------------------- | ---------------------------------------------------------- |
| Who notices first | Users, testers, monitoring                  | The engineers who change the code                          |
| What is wrong     | Behaviour does not match what was specified | The structure makes the next change slower or riskier      |
| How you find it   | Bug reports, failed tests, incidents        | Hotspots, slow lead times, survey answers, static analysis |
| How you rank it   | Severity and how many users it affects      | Interest paid now, and how often the code changes          |
| When it is fixed  | The behaviour is correct                    | The next change in that area is cheaper                    |

The two feed each other. In the [Code Red study of 39 proprietary codebases](https://arxiv.org/abs/2203.04374), published in 2022, low-quality code had 15 times more defects than high-quality code. And bugs deferred from release to release become what a talk at the Dagstuhl seminar calls defect debt, piling up in the issue tracker. Keep each in its own list: bugs by severity in the tracker, debt by interest in a register.

## What does Fowler's technical debt quadrant say?

Fowler's [technical debt quadrant](https://martinfowler.com/bliki/TechnicalDebtQuadrant.html), from October 2009, sorts debt on two questions: was it taken on deliberately or inadvertently, and was it prudent or reckless? The quotes in the cells are from his diagram.

|                 | Reckless                        | Prudent                                       |
| --------------- | ------------------------------- | --------------------------------------------- |
| **Deliberate**  | "We don't have time for design" | "We must ship now and deal with consequences" |
| **Inadvertent** | "What's Layering?"              | "Now we know how we should have done it"      |

- **Prudent and deliberate.** The team borrows knowingly, after weighing whether the payoff of an earlier release beats the cost of repaying. If the interest is small, as in a rarely touched part of the code, it may not be worth paying down at all.
- **Reckless and deliberate.** The team knows better but goes "quick and dirty", which Fowler calls usually reckless, because people underestimate where the design payoff line is.
- **Reckless and inadvertent.** A team that does not know good design practice borrows without realizing it.
- **Prudent and inadvertent.** Debt discovered by learning, "not just common but inevitable for teams that are excellent designers", since it can often take a year of programming on a project before you understand what the best design approach should have been.

Each cell needs a different response. Schedule the prudent, deliberate items. Catch the reckless, deliberate habit in [code review](https://computese.com/essential-tips-for-new-coders/). Answer reckless, inadvertent debt with training and pairing, not blame. Budget for the prudent, inadvertent kind as the price of learning.

## What kinds of technical debt are there?

Debt is not only untidy code. When Google researchers surveyed engineers about the causes of the debt they ran into, the answers settled into [10 categories](https://ieeexplore.ieee.org/document/10109339): migration needed or in progress, documentation, testing, code quality, dead or abandoned code, code degradation, a team lacking expertise, dependencies, a migration poorly executed or abandoned, and the release process. The table groups the kinds a business system usually carries; the examples are illustrative.

| Kind                                                  | Example                                                                            | The interest you pay                                                        |
| ----------------------------------------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| Code                                                  | A 2,000-line pricing module that most changes pass through                         | Each change takes longer and breaks something nearby                        |
| Architecture                                          | Two services that must be released together; many modules writing one shared table | Changes need coordination; no part can be scaled or replaced alone          |
| Tests                                                 | No automated tests around invoicing; a flaky end-to-end suite                      | Manual checks before every release; nobody dares to refactor                |
| Dependencies and end-of-life runtimes (security debt) | An API on Node.js 20, past end of life since 30 April 2026; a web tier on Struts 1 | No security patches; libraries drop support; upgrades arrive as emergencies |
| Infrastructure and release                            | Hand-configured servers; a manual, multi-hour deployment                           | Bigger, riskier releases and slower recovery                                |
| Documentation and knowledge                           | One person understands the nightly batch                                           | Slow onboarding; incidents wait for that person                             |
| Data                                                  | Overloaded columns; a pipeline of ad hoc scripts feeding reports                   | Every report needs cleanup; one input change ripples everywhere             |
| Dead code and stale flags                             | An unused function left in a production system; flags nobody removed               | More paths to test, and old code that can wake up                           |

Dead code is easy to leave off the list, and it can be expensive. The [SEC's announcement](https://www.sec.gov/newsroom/press-releases/2013-222) of its October 2013 order against Knight Capital says that in 2005 Knight moved code in its automated equity router, leaving a function defective: "Although this function was not meant to be used, Knight left it in the router." In late July 2012, Knight incorrectly deployed new code to the same router. On August 1 certain orders triggered the defective function, and in the first 45 minutes after the market opened the router sent more than 4 million orders while trying to fill 212 customer orders. Knight lost more than $460 million. Among the order's charges: Knight did not have adequate controls and procedures for code deployment and testing for that router.

![Cutaway of a router box: two upper pipes pass a few envelopes each. A new module on top is linked to a lever on an old orange pipe below, which sprays a dense fan of envelopes out to the right.](https://computese.com/images/blog/technical-debt/dead-code-wakes.d2b345c448-1536.webp)

*Code that is never meant to run is still part of the system, and one deployment can wake it.*

## How do you measure technical debt?

There is no single number. Google researchers [explored 117 metrics](https://ieeexplore.ieee.org/document/10109339) as indicators of three kinds of debt and found that "no single metric predicted reports of technical debt from engineers." Combined in random forest models, the metrics had high precision (over 80%) but low recall (10% to 25%): they found real problem areas but missed many. So track several signals for each service and watch their trends.

| Signal                               | What it shows                                                         | Where the data comes from                        | What it cannot tell you                                             |
| ------------------------------------ | --------------------------------------------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------- |
| DORA delivery metrics                | Whether changes to one service are slowing down or failing more often | Deployment pipeline and incident records         | Why: debt, staffing and process problems look the same              |
| Hotspots                             | Where change effort concentrates in complex code                      | Version control history and a size measure       | Whether the complexity is essential, or debt in code nobody changes |
| Dependency age and end-of-life dates | How far libraries and runtimes lag behind supported versions          | Lock files, manifests and vendors' support pages | Whether a particular old version is actually risky                  |
| Static analysis                      | Rule violations and an estimate of the effort to fix them             | SonarQube or a similar analyzer                  | The interest, and any problem its rules do not detect               |
| Engineer survey                      | Which kinds of debt slow people down, and where                       | A short survey each quarter                      | Early warning: debt shows up only once it hinders people            |

### Delivery signals: the DORA metrics

[DORA](https://dora.dev/guides/dora-metrics/), a program run by Google Cloud, uses five software delivery performance metrics, up from its original four keys (guide updated January 5, 2026). For throughput: change lead time (from commit to deployed in production), deployment frequency and failed deployment recovery time. For instability: change fail rate (deployments that need immediate intervention) and deployment rework rate (unplanned deployments caused by an incident in production). Our guide to the [software development process](https://computese.com/software-and-development/) explains each one for a non-technical reader.

Debt shows up here as drift: lead time creeping up for one service, or its change fail rate rising. DORA notes that the metrics are best suited to one application or service at a time, and warns that setting them as goals invites teams to game them (Goodhart's law). If your pipeline does not record deployments and their outcomes yet, start there; our CI/CD pipeline guide covers what each stage should gate.

### Hotspots: where change meets complexity

Debt in code that never changes costs little, so look where the changes go. CodeScene's [hotspot documentation](https://codescene.io/docs/guides/technical/hotspots.html) puts it plainly: "Most development activity tends to be located in relatively few modules." Low code health in a hotspot is expensive; in stable code it has lower priority. In the docs' example dashboard, the prioritized hotspots are 1.2% of the codebase but take 12.5% of the development effort and hold 45% of the bugs fixed.

![A city-like grid of low grey blocks seen from above. One block near the centre is much taller, cracked and orange; many arrows point into it and small bug symbols gather at its foot.](https://computese.com/images/blog/technical-debt/hotspot-block.46e86b460a-1536.webp)

*Debt costs most where the code changes most: one busy, tangled file can outweigh a dozen untidy ones nobody touches.*

For a first list without a tool, run these [git log](https://git-scm.com/docs/git-log) options from the repository root. They count the commits that touched each file in the last 12 months, skip merges, and print each file's line count as a rough stand-in for complexity:

```bash
git log --since="12 months ago" --no-merges --format=format: --name-only \
  | grep -v '^$' | sort | uniq -c | sort -rn | head -20 \
  | while read -r changes file; do
      [ -f "$file" ] && printf '%5d changes %7d lines  %s\n' "$changes" "$(wc -l < "$file")" "$file"
    done
```

Check the top files with the team: a settings file can change often and be harmless, while a file every feature must pass through is debt. Counts are by path, so a rename splits a file's history.

The Code Red study, by Adam Tornhill of CodeScene and Markus Borg of RISE and Lund University, [analyzed activity in 30,737 files](https://arxiv.org/abs/2203.04374) and also found that resolving issues in low-quality code took on average 124% more development time, with maximum cycle times 9 times longer. It rates quality with CodeScene's own Code Health metric, so treat the multiples as one well-documented data point, not a law.

### Dependency age and end-of-life dates

Dependencies age even when nobody touches the code, so hotspots miss them. [DORA's code maintainability guidance](https://dora.dev/capabilities/code-maintainability/) warns that "as dependencies age, it is more likely that vulnerabilities will be discovered in them," and suggests measures such as the percentage of applications not using the most recent stable version of all their libraries. Where teams manage dependencies on their own, finding the services affected by a vulnerable library "is typically a substantial archaeology project." A software bill of materials (SBOM) gives you that inventory in advance, and our [secure coding checklist](https://computese.com/best-practices-for-secure-coding/) covers keeping dependencies under control.

For one number, [libyear](https://libyear.com/) adds up how far each dependency is behind its newest release: one dependency a year behind plus one three years behind make a system four libyears old. It does not say whether an old version is vulnerable, so read it beside your scanner's findings and the end-of-life dates below.

### Static analysis: SonarQube's debt ratio and rating

Static analyzers estimate the principal. In [SonarQube Server 2026.5](https://docs.sonarsource.com/sonarqube-server/user-guide/code-metrics/metrics-definition), technical debt is "the sum of the maintainability issue remediation costs," each issue costing the minutes assigned to the rule that raised it; a day of debt is 8 hours. The technical debt ratio divides that by the cost to develop the code, by default 30 minutes per line, and sets the default maintainability rating: A up to 5%, B from 5% to under 10%, C from 10% to under 20%, D from 20% to under 50%, and E at 50% or more.

For example, 120,000 minutes of remediation effort on 50,000 lines gives 120,000 ÷ (30 × 50,000) = 8%, a B, shown as 250 days of debt. The figure covers only what your rules detect, and it estimates the principal, never the interest: the same B costs nothing in a module nobody changes and a great deal in a hotspot. Use the rating as a trend and as a floor for new code.

### Ask the engineers

Having found no leading indicator so far, Google continues to measure debt with a survey. Since 2018 its [quarterly engineering survey](https://ieeexplore.ieee.org/document/10109339) has asked engineers how far they are "hindered by unnecessary complexity and technical debt"; another question asks which of the 10 categories hindered their productivity in the previous quarter. The researchers later added four questions any team can borrow:

- To what extent has your team deliberately incurred technical debt in the past three months?
- How often do you feel that incurring technical debt was the right decision?
- How much did your team invest in reducing existing technical debt and maintaining your code?
- How well does your team's process for managing technical debt work?

A survey is a lagging indicator: debt shows up only once it is severe enough to hinder engineers. Google invites a third of its engineers each quarter and about a third of those respond, so a team of 100 engineers might yield only nine or 10 responses; a small team can ask everyone, anonymously.

## How do you keep a technical debt register?

A register turns complaints into decisions. In the maturity model built by a coalition inside Google, this is what makes a team proactive: such teams "deliberately identify and track technical debt and make decisions about its urgency and importance relative to other work." Keep it short, in your tracker or a file next to the code. The two rows below are examples with illustrative numbers.

| Item                                      | Symptom                                                                                     | Interest paid now                                       | Cost to fix                                                  | Owner             | Trigger to act                       |
| ----------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------------ | ----------------- | ------------------------------------ |
| Pricing rules module (`pricing/rules.py`) | Every pricing change needs a manual regression pass; 2 of the last 6 incidents started here | About 2 extra days on each of 3 pricing changes a month | 3 weeks for 2 engineers, tests first                         | Checkout lead     | Before the Q1 pricing project starts |
| API runtime on Node.js 20                 | Past end of life since 30 April 2026; new library releases need Node.js 22 or later         | No security patches; library upgrades blocked           | About 1 week to move to Node.js 24 LTS and fix failing tests | Platform engineer | Now                                  |

Four rules keep it useful:

- **Write interest in units a sponsor understands:** extra days per change, incidents, hours of manual work per release, weeks to onboard someone. Estimates are fine.
- **Rank by interest, weighted by how often the code changes.** A terrible module nobody touches goes to the bottom.
- **Give every row an owner and a trigger:** a date, an event or a threshold. A row without a trigger is a complaint.
- **Log deliberate debt the day you take it on,** with the reason and an expiry date, and review the register with the product owner at every planning cycle.

## How do you pay technical debt down?

Match the method to the size of the item: small debt is paid in normal work, larger items need time set aside, and whole systems are replaced piece by piece.

### Fix it in the code you are already changing

Fowler's [opportunistic refactoring](https://martinfowler.com/bliki/OpportunisticRefactoring.html) is the default: whenever someone sees code that isn't as clear as it should be, they fix it right there and then, following the camp site rule of leaving the code better than they found it. Gradual cleanup like this lands in the areas you modify most, which, as Fowler notes in his debt article, are exactly where cruft most needs removing. Refactor only when the tests are green, and keep branches short, since feature branches often discourage refactoring by making merges harder. Where tests are missing, write them first; our overview of [test automation in 2026](https://computese.com/navigating-the-future-unveiling-10-cutting-edge-automation-testing-trends-of-2024/) covers the tools and how to fix flaky tests.

### Reserve capacity for the larger items

Some items do not fit inside a feature: a framework upgrade, splitting a module, replacing a test suite. Set time aside for them before planning starts, or they lose every trade-off against features. The share is a decision for the team and its sponsor, not a number from an article: start from the interest in the register, agree a share for the quarter, and check whether the interest fell.

![A long bar of eight equal grey segments. Arrows drop a feature card into each of the first seven; the last segment is lifted, held by brackets, holds a wrench and gear, and is orange.](https://computese.com/images/blog/technical-debt/reserved-capacity.2922b26c6f-1536.webp)

*Setting the share aside before planning keeps debt work from losing every trade-off against features.*

Agree the trigger in advance, too. Google's [example error budget policy](https://sre.google/workbook/error-budget-policy/) does this for reliability: if a service has exceeded its error budget for the preceding four-week window, all changes and releases other than P0 issues or security fixes halt until the service is back within its SLO. A debt version might read: if a service's change fail rate stays above the agreed level for two months, the next cycle goes to the top items in its register.

### Refactor, replace piece by piece, or rewrite

| Approach                               | Fits when                                                               | Main risk                                             |
| -------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------- |
| Refactor in place                      | The design is mostly sound, and tests exist or can be added             | Slow progress if the hotspot is very large            |
| Replace piece by piece (strangler fig) | The system works, but its framework, runtime or structure blocks change | Running old and new side by side for a while          |
| Rewrite from scratch                   | The system is small, or its behaviour is simple and fully specified     | Months without new features while the copy catches up |

Fowler and his colleagues have seen the plan to build a new system that does exactly what the old one does, in better technology, "go down in flames most of the time": replacing a serious system takes a long time, users cannot wait for new features, and the details of existing behaviour are often hard to pin down. The [Strangler Fig](https://martinfowler.com/bliki/StranglerFigApplication.html) alternative begins with small additions "built on top of, yet separate to the legacy code base" and moves behaviour across a piece at a time, through transitional architecture that Fowler judges worth its cost. Our strangler fig pattern guide covers the interception point, what to move first and the shared database.

### Upgrade runtimes and frameworks before end of life

End-of-life runtimes are debt with a due date. When a Node.js version reaches end of life, [it no longer receives updates, including security patches](https://nodejs.org/en/about/eol), and many popular packages drop support for it over time. The vendors' dates as of 9 October 2026:

| Platform   | Version          | Status on 9 October 2026                                                                           | Support ends                                                     |
| ---------- | ---------------- | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| Node.js    | 20               | End of life                                                                                        | 30 April 2026                                                    |
| Node.js    | 22               | Maintenance LTS                                                                                    | 30 April 2027                                                    |
| Node.js    | 24               | Active LTS, maintenance from 20 October 2026                                                       | 30 April 2028                                                    |
| Node.js    | 26               | Current, LTS from 28 October 2026                                                                  | 30 April 2029                                                    |
| .NET       | 8 (LTS), 9 (STS) | Maintenance                                                                                        | 10 November 2026                                                 |
| .NET       | 10 (LTS)         | Active                                                                                             | 14 November 2028                                                 |
| Python     | 3.10             | End of life                                                                                        | 1 October 2026                                                   |
| Python     | 3.11, 3.12, 3.13 | Security fixes only                                                                                | October 2027, October 2028, October 2029                         |
| Python     | 3.14             | Bug fixes                                                                                          | October 2030                                                     |
| Oracle JDK | 8, 11            | Extended Support                                                                                   | December 2030, January 2032                                      |
| Oracle JDK | 17               | Premier Support listed to September 2026 (or later); Extended Support fee waived from October 2026 | Extended Support until September 2029 (or later)                 |
| Oracle JDK | 21, 25           | Premier Support                                                                                    | Extended Support until September 2031, September 2033 (or later) |

The dates come from the [Node.js releases page](https://nodejs.org/en/about/previous-releases) and [release schedule](https://github.com/nodejs/Release), the [.NET support policy](https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core), the [status of Python versions](https://devguide.python.org/versions/) and the [Oracle Java SE support roadmap](https://www.oracle.com/java/technologies/java-se-support-roadmap.html). Node.js notes that its dates are subject to change, and Oracle's dates apply only to the commercially supported Oracle JDK.

> [!WARNING]
> .NET 8 and .NET 9 both reach end of support on 10 November 2026, and Node.js 20 and Python 3.10 are already past end of life. Vulnerabilities found after those dates are not fixed in them, so plan the move now.

Plan each upgrade a cycle ahead. Node.js LTS lines typically get critical bug fixes for a total of 30 months, .NET LTS releases get free support and patches for three years (STS releases for two), and Python support ends five years after a release. The runtime a system launches on will need replacing while the system is young, so put the next upgrade in the register on launch day, with its end-of-life date as the trigger.

![Two rails. Shields fall on a server on the lower rail until a short post; past it the rail runs bare to a broken end. An orange arrow lifts the server to the upper rail, where shields keep falling.](https://computese.com/images/blog/technical-debt/eol-upgrade.31c961417b-1536.webp)

*Patches stop on the end-of-life date, so the move has to be finished before it, not started on it.*

Frameworks follow the same arithmetic. The Apache Struts team [declared Struts 1 end of life](https://struts.apache.org/struts1eol-press) on 5 April 2013, more than four years after its last release (1.3.10, in December 2008), and warned that security and bug fixes would no longer be provided. Systems built on it still run: our [case study on a utility's legacy Java system](https://computese.com/work/legacy-java-modernization/) starts from a core system that had run for more than 15 years on Struts 1, Java 8 and WebLogic, and shows how a move to Spring Boot on Java 21 was proven in code.

Between the big moves, let a bot keep libraries current. [Dependabot](https://docs.github.com/en/code-security/reference/supply-chain-security/dependabot-options-reference) reads its settings from [`.github/dependabot.yml`](https://docs.github.com/en/code-security/concepts/supply-chain-security/about-the-dependabot-yml-file) on the default branch. This configuration groups all minor and patch updates for npm into one weekly pull request, leaves each major update in a pull request of its own, and checks GitHub Actions monthly:

```yaml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    groups:
      minor-and-patch:
        applies-to: version-updates
        patterns:
          - "*"
        update-types:
          - "minor"
          - "patch"
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "monthly"
```

By default Dependabot keeps at most five version-update pull requests open (`open-pull-requests-limit`; security updates do not count toward it), and it does not propose a new version as a version update until 3 days after its release (`cooldown`).

## How do you stop new debt from piling up?

Paying debt down is wasted effort if the same habits create it again. Three guards are cheap to run.

### Gate new code, not old code

SonarQube's default [quality gate](https://docs.sonarsource.com/sonarqube-server/quality-standards-administration/managing-quality-gates/introduction-to-quality-gates), Sonar way, "focuses on keeping high quality standards for new code, rather than spending a lot of effort remediating old code." Its four conditions: no new issues are introduced, all new Security Hotspots are reviewed, new code test coverage is at least 80.0%, and duplication in new code is at most 3.0%. A fudge factor, on by default, ignores the duplication condition until there are at least 20 new lines and the coverage condition until there are at least 20 new lines to cover, to avoid overly strict enforcement on small changes. Because SonarQube counts [recently added or modified](https://docs.sonarsource.com/sonarqube-server/user-guide/code-metrics/metrics-definition) code as new code, a gate on every pull request also covers old code as it is touched. A second built-in gate, Sonar way for agentic AI, is recommended for agent-centric development; our look at [the future of software engineering](https://computese.com/the-future-role-of-software-engineers/) summarizes what research says about AI coding tools.

![A conveyor carries new blocks toward a wall of old grey code blocks, some cracked. A check-post's orange barrier arm lies across the belt in front of a cracked new block; smooth blocks pass on to the wall.](https://computese.com/images/blog/technical-debt/quality-gate.5389c6c3d4-1536.webp)

*The gate holds new and changed code to the standard and leaves the rest alone, so it can start today without a cleanup project.*

### Give every shortcut an expiry date

Feature flags are deliberate debt by design. Pete Hodgson's [guide to feature toggles](https://martinfowler.com/articles/feature-toggles.html) says savvy teams view their toggles as inventory that comes with a carrying cost and keep that inventory as low as possible. Some teams add a toggle removal task to the backlog whenever a release toggle is introduced, others put expiration dates on their toggles, and some create "time bombs" that fail a test, or even refuse to start the application, if a flag is still around after its expiration date. A time bomb takes a few lines in the test suite:

```python
from datetime import date

# name: (owner, expiry date agreed when the flag was added)
FLAGS = {
    "new_checkout": ("payments team", date(2026, 12, 31)),
    "bulk_export": ("reporting team", date(2027, 2, 28)),
}


def test_no_flag_is_past_its_expiry():
    expired = [
        f"{name} (owner: {owner}, expired {expires})"
        for name, (owner, expires) in FLAGS.items()
        if expires < date.today()
    ]
    assert not expired, "Remove or extend, with a reason: " + ", ".join(expired)
```

Read the names and dates from wherever your flags are defined. The same works for any deliberate shortcut, such as a prototype or a temporary integration: give it an owner and a date in the register, and make the date fail something visible.

### Keep changes small and reviewed

DORA names reducing the batch size of changes as a common way to improve all five metrics, since smaller changes are easier to reason about, to move through delivery and to recover from. They are also easier to review: ask of each change whether it adds debt and, if so, whether it is in the register with an owner and a date.

## How do you explain technical debt to the business?

A sponsor needs the trade in their own terms, and four costs translate directly:

- **Cost of delay.** Features in the debt-heavy area take longer: six days instead of four in Fowler's example, times the changes planned there.
- **Risk.** An end-of-life runtime gets no security patches, and an unused code path can be woken by a deployment, as Knight Capital's was.
- **Incidents.** Count the incidents and hotfixes that started in the item's code, and show the service's change fail rate and deployment rework rate.
- **Onboarding and key people.** How long a new engineer takes to make a safe change there, and how many people can. Cunningham warned in 1992 that excess quantities of immature code make a program unmasterable, "leading to extreme specialization of programmers and finally an inflexible product."

A good request names one item and reads like any other investment case. An example, with illustrative numbers:

| Part of the request         | Example                                                                                          |
| --------------------------- | ------------------------------------------------------------------------------------------------ |
| The item                    | The pricing rules module                                                                         |
| Interest now, with evidence | About 2 extra days on each of 3 pricing changes a month; 2 of the last 6 incidents started there |
| What the team will do       | Add tests around current behaviour, then split the module; no feature changes                    |
| Cost                        | 3 weeks for 2 engineers this quarter                                                             |
| What should change          | Pricing changes back to about 4 days, and fewer hotfixes for the service                         |
| When you will check         | At the end of next quarter, from the register and the DORA numbers                               |
| Risk of waiting             | The Q1 pricing project lands in this module                                                      |

> [!TIP]
> Ask for a named item and its interest, never for a "refactoring sprint". A sponsor can weigh two saved days per pricing change against a feature; an abstract quality goal gives them nothing to weigh.

Report back afterwards: did the interest fall? Showing that the last loan was repaid is the best argument for the next one.

## When is technical debt the right call, and what breaks in year two?

Debt is a tool. "Not all debt is bad, but all debt needs to be serviced," write the Google authors of [Hidden Technical Debt in Machine Learning Systems](https://proceedings.neurips.cc/paper_files/paper/2015/hash/86df7dcfd896fcaf2674f757a2463eba-Abstract.html), and Jaspan and Green note that "zero technical debt is not the goal anyway." Deliberate debt is the right call when:

- **You are testing an idea.** A prototype or first MVP exists to learn whether anyone wants the thing, and polishing code you may delete is waste. Write the expiry into the plan: the date or milestone at which it is rebuilt or retired.
- **A date really is fixed,** such as a regulatory deadline, and the shortcut sits in code you can name.
- **The interest is close to zero** because the code rarely changes, which is Fowler's case for leaving prudent debt unpaid.

It is the wrong call when the shortcut lands in a hotspot, when nobody writes it down, or when "temporary" has no date. Fowler warns that teams who take on debt to rush urgent features end up "maxing out all their credit cards, but still delivering later than they would have done had they put the effort into higher internal quality," and that teams hit the design payoff line "in weeks rather than months."

![Two panels. Left: a thin orange scaffold holds one small block, with a pennant and a blank tag. Right: the same scaffold sits under a tall stack of blocks, its poles bowing, two blocks above it cracked.](https://computese.com/images/blog/technical-debt/prototype-scaffold.6bfccb4f86-1536.webp)

*A shortcut with no expiry date can end up as the foundation everything after it is built on.*

If nobody tracks the debt, year two is predictable:

| Symptom in year two                                           | Usual cause                                                    | Fix                                                                   |
| ------------------------------------------------------------- | -------------------------------------------------------------- | --------------------------------------------------------------------- |
| The prototype is now the product, and every feature fights it | Its expiry passed without a decision                           | Decide now: replace it piece by piece, or keep it and record the debt |
| A scanner flags the runtime                                   | The runtime the system launched on is near or past end of life | Upgrade on a schedule, with the date in the register                  |
| Releases slow down and hotfixes rise                          | Debt in the busiest modules, and tests that never got written  | The hotspot list, tests first, reserved capacity                      |
| Old flags and branches that nobody dares to delete            | No removal task or expiry when they were added                 | An expiry test, and a removal task with every flag                    |
| Two versions of the same thing in production                  | A migration abandoned partway, one of Google's 10 categories   | Finish it or roll it back; do not keep both                           |
| Only one person can change the billing code                   | Knowledge never written down, then staff turnover              | Pairing, runbooks and decision records                                |

## A technical debt plan you can start this month

1. **Pick one service** that matters to the business, and record its five DORA metrics for the last quarter.
2. **Run the hotspot query** on its repository and mark the files the team calls painful.
3. **Ask the four survey questions** anonymously, and set the answers beside the hotspot list.
4. **List every runtime and framework** with its end-of-life date from the vendor, and flag anything past its date or within a year of it.
5. **Start the register** with the five to ten items with the highest interest, each with an owner and a trigger.
6. **Agree the capacity share and the trigger rule** with the sponsor, in writing.
7. **Turn on the guards:** a quality gate on new code, a dependency bot and expiry dates on flags.
8. **Review after a quarter:** did the interest fall on the items you paid down, and did the service's DORA numbers move?

If you would rather start with an outside view, Computese's [custom software development](https://computese.com/services/custom-software-development/) team takes over software other teams built, starting with a fixed-scope codebase review: we reproduce the build and deploy, run the tests and security scans, read the data model, and return a ranked fix list with a straight answer on repair versus rebuild.

## Key terms
- **Technical debt**: Design or implementation shortcuts that are expedient in the short term but make future changes more costly or impossible. The Dagstuhl 16162 definition limits its impact to internal qualities, mainly maintainability and evolvability.
- **Principal**: What it costs to remove the debt. The Dagstuhl report describes the current principal as the cost that it would now take to develop a different or better solution.
- **Interest**: The extra cost paid while the debt stays: slower changes, induced defects and lost quality. Martin Fowler notes that you pay it only when you work on that part of the code.
- **Technical debt quadrant**: Martin Fowler's 2009 classification of debt along two axes: deliberate or inadvertent, and reckless or prudent.
- **Hotspot**: A file or module where much of the development activity happens. Low code health in a hotspot is expensive; the same problem in stable code has lower priority.
- **DORA metrics**: DORA's five software delivery performance metrics: change lead time, deployment frequency and failed deployment recovery time for throughput, and change fail rate and deployment rework rate for instability.
- **Technical debt ratio**: In SonarQube, the estimated cost to fix maintainability issues divided by the estimated cost to develop the code, at 30 minutes per line by default. It sets the A to E maintainability rating.
- **Libyear**: A measure of dependency freshness: how many years each dependency is behind its newest release, added up across a project.
- **End of life (EOL)**: The date after which a runtime, framework or library gets no further updates from its maintainers, including security patches.
- **Technical debt register**: A short list of known debt items, each with its symptom, the interest it costs now, the cost to fix, an owner and a trigger to act.

## Common questions

### What is technical debt in simple terms?

It is the extra work every change costs because the code, design or tooling is not in the shape the system now needs. Fixing it is the principal; the extra time, defects and risk each change carries until then are the interest. Ward Cunningham coined the metaphor; it appears in his 1992 OOPSLA experience report.

### What are some examples of technical debt?

A module everyone is afraid to change, no automated tests around billing, a runtime or framework past its end of life (Node.js 20, Struts 1), manual deployments, feature flags nobody removed, a batch job only one person understands, and data pipelines held together by ad hoc scripts. Google's engineering survey sorts debt into 10 categories, including migrations, documentation, testing, dead code and dependencies.

### What is the difference between technical debt and a bug?

A bug is wrong behaviour that users or tests can see. Technical debt is internal: the 2016 Dagstuhl definition limits its impact to internal qualities such as maintainability and evolvability, so software can work correctly while every change costs more. Debt makes bugs more likely, and bugs deferred release after release can pile up as defect debt.

### How do you measure technical debt?

No single number captures it: Google explored 117 candidate metrics and no single one predicted what engineers reported. Combine signals for each service instead: DORA delivery metrics, hotspots from version control, dependency age and end-of-life dates, static analysis such as SonarQube's debt ratio, and a short engineer survey. Track the trend of each.

### How much time should a team spend on technical debt?

Treat it as a decision the team and its sponsor make, not a fixed percentage, and revisit it each quarter. Fix small debt in the code you are already changing, reserve a share of each cycle for the larger items in the register, and agree in advance on a trigger that moves more time to debt, such as a change fail rate that stays high.

### Is technical debt always bad?

No. Deliberate, prudent debt, such as a prototype shipped to learn from users, can be the right choice, and Google's researchers write that zero technical debt is not the goal. It turns bad when nobody records it, when it sits in code that changes often, or when it is never repaid.

### What is the technical debt quadrant?

Martin Fowler's 2009 model sorts debt on two axes: deliberate or inadvertent, and reckless or prudent. Its cells read "We don't have time for design" (reckless, deliberate), "We must ship now and deal with consequences" (prudent, deliberate), "What's Layering?" (reckless, inadvertent) and "Now we know how we should have done it" (prudent, inadvertent), which Fowler calls inevitable for teams that are excellent designers.

## Sources
1. [The WyCash Portfolio Management System (OOPSLA '92 experience report)](https://c2.com/doc/oopsla92.html), Ward Cunningham
2. [Ward Explains Debt Metaphor (transcript of his video)](https://wiki.c2.com/?WardExplainsDebtMetaphor), C2 wiki
3. [Technical Debt](https://martinfowler.com/bliki/TechnicalDebt.html), Martin Fowler
4. [Managing Technical Debt in Software Engineering (Dagstuhl Seminar 16162 report)](https://drops.dagstuhl.de/storage/04dagstuhl-reports/volume06/issue04/16162/DagRep.6.4.110/DagRep.6.4.110.pdf), Dagstuhl Reports
5. [Code Red: The Business Impact of Code Quality (Tornhill and Borg, 2022)](https://arxiv.org/abs/2203.04374), arXiv
6. [Technical Debt Quadrant](https://martinfowler.com/bliki/TechnicalDebtQuadrant.html), Martin Fowler
7. [Defining, Measuring, and Managing Technical Debt (Jaspan and Green, 2023)](https://ieeexplore.ieee.org/document/10109339), IEEE Software
8. [Hidden Technical Debt in Machine Learning Systems (Sculley et al., 2015)](https://proceedings.neurips.cc/paper_files/paper/2015/hash/86df7dcfd896fcaf2674f757a2463eba-Abstract.html), NeurIPS Proceedings
9. [SEC Charges Knight Capital With Violations of Market Access Rule](https://www.sec.gov/newsroom/press-releases/2013-222), U.S. Securities and Exchange Commission
10. [DORA's software delivery performance metrics](https://dora.dev/guides/dora-metrics/), DORA
11. [Technical Debt: hotspots and code health](https://codescene.io/docs/guides/technical/hotspots.html), CodeScene documentation
12. [git-log documentation](https://git-scm.com/docs/git-log), Git
13. [Code maintainability](https://dora.dev/capabilities/code-maintainability/), DORA
14. [libyear: a simple measure of software dependency freshness](https://libyear.com/), libyear.com
15. [Understanding measures and metrics (SonarQube Server 2026.5)](https://docs.sonarsource.com/sonarqube-server/user-guide/code-metrics/metrics-definition), Sonar Documentation
16. [Opportunistic Refactoring](https://martinfowler.com/bliki/OpportunisticRefactoring.html), Martin Fowler
17. [Example Error Budget Policy](https://sre.google/workbook/error-budget-policy/), Google SRE Workbook
18. [Strangler Fig](https://martinfowler.com/bliki/StranglerFigApplication.html), Martin Fowler
19. [End-Of-Life](https://nodejs.org/en/about/eol), Node.js
20. [Node.js Releases](https://nodejs.org/en/about/previous-releases), Node.js
21. [Node.js Release Working Group: release schedule](https://github.com/nodejs/Release), Node.js on GitHub
22. [.NET and .NET Core Support Policy](https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core), Microsoft .NET
23. [Status of Python versions](https://devguide.python.org/versions/), Python Developer's Guide
24. [Oracle Java SE Support Roadmap](https://www.oracle.com/java/technologies/java-se-support-roadmap.html), Oracle
25. [Apache Struts 1 End-Of-Life (EOL) Press Release](https://struts.apache.org/struts1eol-press), Apache Struts
26. [Dependabot options reference](https://docs.github.com/en/code-security/reference/supply-chain-security/dependabot-options-reference), GitHub Docs
27. [About the dependabot.yml file](https://docs.github.com/en/code-security/concepts/supply-chain-security/about-the-dependabot-yml-file), GitHub Docs
28. [Understanding quality gates (SonarQube Server 2026.5)](https://docs.sonarsource.com/sonarqube-server/quality-standards-administration/managing-quality-gates/introduction-to-quality-gates), Sonar Documentation
29. [Feature Toggles (aka Feature Flags)](https://martinfowler.com/articles/feature-toggles.html), Pete Hodgson, martinfowler.com
