Blog

What is technical debt? Types, causes and how to pay it down

How to recognize each type of tech debt and what studies found when teams started writing code with AI.

Alex Mercer

Tech debt, short for technical debt, is code or design that makes later changes cost more than they should. It can start as the quick option at the time or as code that no longer fits what the system does. The extra effort on each change is the interest, and you keep paying it until someone cleans up the code. Ward Cunningham introduced the metaphor in 1992.

AI coding agents add to it when they write code faster than anyone cleans it up. Debt is the maintainability side of the risk in AI-written code, and AI code security covers the security side.

What is technical debt?

Martin Fowler calls the underlying 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, a feature that would take four days in a clear module structure takes six in a confusing one. The two extra days are the interest. Cleaning up the structure would take five days, which pays off the principal. For a single feature, cleaning up doesn't pay (nine days instead of six). If two more similar features are coming, cleaning up first is faster overall.

At a 2016 Dagstuhl seminar on managing technical debt, attendees agreed on a working definition:

"In software-intensive systems, technical debt is 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. Technical debt presents an actual or contingent liability whose impact is limited to internal system qualities, primarily maintainability and evolvability."

Where the term comes from

Cunningham's experience report for OOPSLA 1992 was about WyCash, a portfolio management system his team built in Smalltalk. "Shipping first time code is like going into debt," he wrote. "A little debt speeds development so long as it is paid back promptly with a rewrite."

In a 2009 video (transcript on his wiki), he said he coined the metaphor to explain the refactoring on WyCash to his boss, using a financial analogy for financial software. He also said a lot of bloggers had confused it with writing code poorly and planning to do a good job 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."

In the original sense, debt is the gap between what the code says and what the team now understands, and you repay it by refactoring the code to match what you learned. Skip that for long enough and, in his words, "all efforts to work on it take longer and longer."

Types of technical debt

Researchers sort debt by where it sits in the system. Zengyang Li, Paris Avgeriou and Peng Liang reviewed 94 studies published from 1992 to 2013 and grouped the debt they describe into ten types, each with sub-types (Journal of Systems and Software, 2015).

Type

What it is

Examples from the review

Code debt

Poorly written code that breaks coding rules or good practice

Duplicated code, complex code

Design debt

Shortcuts taken in detailed design

Code smells, complex classes or methods

Architecture debt

Architecture decisions that give up internal quality, such as maintainability

Violations of good architecture practice, architecture anti-patterns

Test debt

Shortcuts taken in testing

Missing tests, low coverage, no test automation

Documentation debt

Insufficient, incomplete or outdated documentation

Out-of-date architecture docs, no code comments

Build debt

Flaws in the build system or process that make builds complex and hard to work with

A manual build process, bad dependencies

Infrastructure debt

A poor setup of development processes, technologies and tools

Old technology still in use, no continuous integration

Requirements debt

The gap between the best requirements spec and what was built

Over-engineering

Versioning debt

Problems in source code versioning

Unnecessary code forks

Defect debt

Defects, bugs or failures found in the system

Known bugs still in the code

Code debt is the type those studies mention most (38 of 94), but practitioners point at architecture. Neil Ernst and colleagues surveyed 1,831 people, mostly software engineers and architects at three large organizations, and found that architectural decisions were the most important source of technical debt (ESEC/FSE 2015).

Li and colleagues file code smells under design debt. The code smells guide covers twelve common ones, each with the refactoring that fixes it, and which ones you can spot in a diff.

The four types of technical debt in Fowler's quadrant

Fowler's technical debt quadrant (2009) sorts debt by how the team took it on. One axis is deliberate or inadvertent, the other reckless or prudent.


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"

Deliberate and reckless debt comes from a team that knows good design but goes "quick and dirty" because it thinks it can't afford the time. Fowler argues this usually backfires, because teams reach the point where good design pays off "in weeks rather than months." Deliberate and prudent debt is a considered trade. The team knows it's taking on debt to release sooner and has weighed that payoff against the cost of paying it off.

A team that doesn't know good design practice takes on inadvertent and reckless debt without realizing it. Inadvertent and prudent debt is what good teams find once they learn what the design should have been, which Fowler says can take a year of programming on a project. He writes that this kind "is inevitable and thus should be expected," and that it's the kind of debt Cunningham talked about in his video.

How AI-written code adds to technical debt

Fowler writes that cruft "accumulates where developers make changes without paying attention to internal quality," and that "the more changes, the greater risk of cruft building up." Coding agents make it cheap to change a lot of code at once, and two recent studies measured what that does to code quality.

Hao He and colleagues at Carnegie Mellon (MSR 2026) compared 806 open-source GitHub projects that adopted Cursor between January 2024 and March 2025 with 1,380 similar projects that didn't. Lines added rose 281.3% in the first month after adoption and 48.4% in the second, and after two months the speedup was gone. Static analysis warnings rose 30.3% and code complexity 41.6%, and both stayed up. The authors call it "persistent technical debt accumulation." In their models, doubling code complexity cut later development velocity, measured in lines added, by 64.5%. The projects in the comparison group may use other AI tools. The study covers open-source projects, mostly in TypeScript, JavaScript and Python, during a period when the tools changed fast.

A controlled experiment found no consistent sign that AI-assisted code was harder for other developers to change, within the scope of its tasks. Markus Borg and colleagues ran it in two phases, with 151 participants in all, 95% of them professional developers. First, participants added a feature to a Java web application, with or without an AI assistant. Then new participants evolved that code without AI. Whether the original code was written with AI made no significant difference to how long their changes took or to code quality (Empirical Software Engineering, 2026). As risks to study next, the authors name code bloat from excessive code generation and "cognitive debt" as developers offload more mental effort to assistants.

Surveys are mixed too. In 2024, DORA found that AI adoption negatively impacted software delivery stability and throughput. Its 2025 report, based on a survey of nearly 5,000 technology professionals, found that AI adoption now improves throughput but still increases delivery instability. In that survey, 59% of respondents said AI had improved their code quality and 10% saw a negative effect.

AI-written code still has issues for review to catch, and how many varies by model. Bug Bench, cubic's public benchmark, counts a bug when a cubic review finding on AI-written code gets a developer's upvote or is addressed by an AI coding agent. Its ranking for code-quality issues, the bug type closest to debt, runs from 0.88 for GPT-5.6 Sol to 1.31 for GPT-6 Astra across five models, where 1.00 is the all-model rate and lower is better. Both ends are statistically tied with other models (on Bug Bench, two models are tied when their 95% confidence intervals overlap). Bug Bench doesn't measure debt itself, and it's observational: teams choose their models, so a rate reflects the code each model was used for as well as the model.

What to check in an AI-written pull request

Moritz Beller and colleagues classified over 1,400 changes made during review in two open-source projects and found a 75:25 ratio of maintainability-related to functional problems (MSR 2014), similar to earlier studies of industrial and academic code. For an AI-written pull request, a reviewer looking for debt can ask:

  • Does a helper like this already exist somewhere else in the codebase?

  • Is there a new abstraction with only one caller?

  • Do tests cover the new paths, or only the happy path?

  • Did the change add a dependency, and is it maintained?

  • Does it follow the team's conventions, or start a second way of doing the same thing?

The first and last questions need context beyond the diff, which AI code review adds by reading each pull request in the context of your codebase. You can check for some of these patterns on every pull request with cubic's public maintainability rule. It flags duplicated helpers, copy-pasted logic, pass-through wrappers and "temporary" branching likely to become permanent debt. Its text can also go in AGENTS.md so coding agents follow it while they write.

How to measure technical debt

There's no single number for technical debt. Fowler points out that none of its costs are objectively measurable, and that estimates of the interest and the principal aren't very accurate. You can still measure proxies that show where debt costs you.

Proxy

What it tells you

How to get it

Churn on hotspots

Where you pay interest most often

Commits per file from git history

Complexity

How hard a function is to read and test

Cyclomatic complexity from your linter

Coverage on changed code

How safe it is to refactor there

Your coverage report, filtered to busy files

Defect and rework rates

Where debt turns into bugs and unplanned work

Bug-fix commits per file, DORA's change fail rate and deployment rework rate

TODO and FIXME count

Debt the team already wrote down

git grep

Dependency age

How far behind libraries and runtimes are

Your package manager's outdated report

Churn and hotspots

Start with git history. This lists the files changed most often in the last year:

git log --since="12 months ago" --format=format: --name-only \
  | grep -v '^$' | sort | uniq -c | sort -rn | head -20
git log --since="12 months ago" --format=format: --name-only \
  | grep -v '^$' | sort | uniq -c | sort -rn | head -20
git log --since="12 months ago" --format=format: --name-only \
  | grep -v '^$' | sort | uniq -c | sort -rn | head -20

Add -i --grep="fix" to the git log part to count only commits that mention a fix, which gives a rough map of where bugs get fixed.

Churn alone isn't debt, since busy files are often just important files. A hotspot is a file that changes often and is hard to change. In a study of Windows Server 2003 (ICSE 2005), Nachiappan Nagappan and Thomas Ball found absolute churn a poor predictor of defect density. Churn measured relative to things like component size was highly predictive, and their measures told fault-prone binaries from the rest with 89.0 percent accuracy.

Complexity and the debt you already know about

Cyclomatic complexity counts the independent paths through a function, so it tracks how hard the function is to test and reason about. Linters such as ESLint and Pylint can report it. Track it in your hotspots, since a codebase-wide average hides the few functions that matter. Code quality metrics that predict production incidents compares it with churn and coverage as a predictor of bugs.

Then list the TODO, FIXME and HACK comments with git grep -nE "TODO|FIXME|HACK" and ask the team which part of the codebase they avoid, as a cheap check on what the numbers say.

How to prioritize technical debt

You only pay interest when you change the code. In Fowler's words, "crufty but stable areas of code can be left alone. In contrast, areas of high activity need a zero-tolerance attitude to cruft, because the interest payments are cripplingly high."


Changes often

Rarely changes

Hard to work in

Fix first. Most of the interest goes here.

Leave it, unless it carries risk

Easy enough to work in

Clean up as you go, in feature PRs

Ignore

Rank debt by the interest it charges and the risk it carries. Ugly code nobody touches can wait, while debt in auth or payment code, or on a runtime that's losing support, moves up even if it rarely changes. Write each item down with its location, the interest (what it slows down) and a rough principal (what fixing it costs), in the same tracker as the rest of the work.

Fowler notes that the metaphor is "very handy for communicating to non-technical people." A line like "this module adds about two days to every billing feature, and fixing it takes a week" is a trade a product manager can weigh.

How to pay down technical debt

  1. Refactor where you're already working. Fowler's advice is to pay the principal off gradually, because "we spend more time on removing cruft in those areas that we modify frequently, which are exactly those areas of the code base where we most need the cruft to be removed."

  2. Budget steady time for items too big for a feature PR, such as a schema migration, a framework upgrade or splitting a module, so they don't wait for a quiet quarter that never comes.

  3. Before restructuring a hotspot, add tests that pin down how it behaves today.

  4. Keep refactoring in its own commit or PR, apart from behavior changes, so it's easier to review and to revert.

  5. Stop new reckless debt at review with linter thresholds on new code, a definition of done that includes tests, and cleanup before merge instead of after.

  6. Check that it worked by watching the same proxies in the area you fixed, such as bug-fix commits and how long changes there take.

FAQ

What are some examples of tech debt?

A function copied into three services instead of shared is code debt. Test debt can be a suite nobody trusts, so people skip it. Architecture debt shows up as a database schema designed for one product line that now carries three, and infrastructure debt as a framework two major versions behind, with a security fix you can't apply without a migration.

Is tech debt a bug?

Researchers disagree. In Li, Avgeriou and Liang's review, 11 studies count defects as technical debt, while four argue they aren't, because technical debt is about internal quality that users can't see. The 2016 Dagstuhl definition fits the second view, since it limits technical debt's impact to internal qualities such as maintainability and evolvability.

How much technical debt is acceptable?

It depends on where the debt sits. Fowler notes that prudent debt taken on to reach a release "may not be worth paying down if the interest payments are sufficiently small," for example in a rarely touched part of the code. A workable rule is to keep hotspots clean and track the rest, then act when the interest shows up as slower changes or repeated bugs.

Is AI creating technical debt?

It can. In the Cursor study above, the speedup faded, but the extra static analysis warnings and complexity stayed and slowed later work. The phrase "technical debt in AI" can also mean debt inside machine learning systems: in a 2015 paper, D. Sculley and colleagues at Google found it common to incur massive ongoing maintenance costs in real-world ML systems, from risks such as entanglement, hidden feedback loops and data dependencies. As they put it, "Not all debt is bad, but all debt needs to be serviced."

Table of contents