Tech debt, short for technical debt, is the extra work every future change costs because of a shortcut taken earlier: the skipped fix is the principal, and the time each later change loses is the interest. Of the 26 apps whose code I audited in mid-2026, at least 23 had zero working automated tests.

What tech debt is: the definition of technical debt, and what counts

Technical debt counts anything in the code or its setup that makes the next change slower or riskier than it needs to be: missing tests, duplicated logic, outdated dependencies, missing documentation, a deploy only one person can run. A bug does not count, and neither does a missing feature or code someone simply dislikes.

Those 26 are the real applications I audited between June and July 2026, in three cohorts. I chose them for review, so the tests figure applies to those apps alone and is not a rate for apps in general. Keeping debt like that visible belongs with the other engineering standards for AI-assisted teams, the ones a small team can check without reading code.

Ward Cunningham coined the debt metaphor to explain the refactoring his team was doing on WyCash, a piece of financial software: if the program did not match what the team now understood, they would keep stumbling over the mismatch, and that slowdown was like paying interest on a loan. In Ward Cunningham’s own explanation of the metaphor he also says what it did not mean: in his view, a lot of bloggers confused it with writing code poorly and planning to do a good job later, and he is “never in favor of writing code poorly”. What he meant by technical debt was code written to match a partial understanding of the problem, repaid by refactoring once the team knows more.

In an app, the principal is the fix nobody made: the test never written, the rule pasted into a third file. The interest is what each later change pays for it, in extra hours of reading and in bugs in the copy nobody updated.

Technical debt is the formal term and tech debt is what engineers say out loud; the two mean the same thing. Technological debt and technology debt are other spellings of technical debt, not a separate kind. In an IT department, “IT debt” usually means something wider: aging systems, licenses nobody reviewed and servers nobody patched. In software development, technical debt is narrower, because its cost lands on the next code change.

To define tech debt for the rest of a team, ask of each item whether it slows down or endangers a change someone wants to make. Old code is not considered technical debt by itself; it becomes debt when a change has to work around it. A definition of tech debt that holds up in a planning meeting ties every item to a change on the roadmap.

The technical debt quadrant: deliberate or inadvertent, prudent or reckless

The technical debt quadrant is Martin Fowler’s two-by-two for sorting debt by how it was taken on: deliberately or inadvertently, and prudently or recklessly. Its value for an owner is the action each cell calls for: record the deliberate, prudent kind with a trigger to repay it, and stop the reckless kind before it merges.

Martin Fowler’s technical debt quadrant is a post from October 2009. His argument is that the useful line runs between prudent and reckless debt, not between debt and non-debt, and that deliberate and inadvertent debt differ too. He also holds that the odd-sounding fourth cell, prudent and inadvertent, is “not just common but inevitable for teams that are excellent designers”. His figure gives each cell of the tech debt quadrant a line someone might say. The first column below uses those labels; the other two columns are my own take on what each cell looks like in a small app and what an owner does about it.

Cell, in Fowler’s labelsWhat it looks like in a small appWhat to do about it
Deliberate and prudent: “We must ship now and deal with consequences”A known shortcut to hit a launch date, such as a plan limit hard-coded in one placeWrite it in the debt register with the event that triggers repayment
Deliberate and reckless: “We don’t have time for design”A feature merged with no tests and copied logic because the week was fullStop it at review, before it merges
Inadvertent and reckless: “What’s Layering?”Database calls inside screen components, written by someone or something that did not know betterCatch it earlier with tests and linters
Inadvertent and prudent: “Now we know how we should have done it”A data model that made sense at launch and fits badly now the product is clearerRefactor when the better design is clear, one area at a time as changes reach it

Other lists sort debt by where it sits: code debt, design debt, test debt, documentation debt or infrastructure debt; the quadrant sorts it by how it got there, which is what decides the action. How the quadrant reads for AI-written code, and the answer to “What are the four types of technical debt?”, sit in the FAQ of the article on technical debt in AI-generated code, which the section on why it matters links.

A technical debt example in an AI-built app, and four more

A technical debt example: a pricing rule written in three places. The interest is that every price change needs three edits, and missing one gives two customers two prices. Paying it off means one function that all three places call, with a test on it. The table below gives four more, each with its interest.

Take it as an illustrative case. An app built quickly by prompting gets a discount rule in the checkout, the invoice and the admin screen, each as its own piece of code. Nothing is wrong on launch day. The first price change edits two of the three, and a customer sees one total at checkout and another on the invoice.

The table puts that case in its second row and adds four more examples of tech debt that small apps carry. Its “interest” and “paying it off” columns are judgment calls of mine.

The debtThe interest you payWhat paying it off looks like
No automated testsEvery change is checked by clicking through the app, and regressions reach customers firstSmoke tests on the flows that make or lose money, run on every change
The same business rule in three placesEach change needs every copy edited, and a missed copy is a bugOne function every caller uses, with a test on it
Untyped data crossing the API boundaryThe backend renames a field and the frontend breaks in production without a warningShared types, or a schema checked on both sides
A framework pinned to an old versionSecurity fixes stop reaching the app, and the eventual upgrade jumps several versions at onceUpgrades in small steps, with the tests run between them
A deploy only one person can doReleases wait for that person, and nobody else can roll backA written, scripted deploy that someone else has run

The first row is the gap the opener counted. In the same audits, 9 of the 26 apps ran a framework version with a publicly known, reachable RCE (remote code execution) or auth bypass, and the fix was often a one-line version bump. In agile terms, each of these technical debt examples becomes a backlog item with an owner and a test for done. Dependencies carry a second kind of debt, licenses nobody read, which is the job of license scanning.

Code rot: why code that nobody touched still breaks

Code rot is software that breaks without anyone changing it, because its surroundings moved: a runtime reached end of life, an API version was switched off, a certificate or root expired, a package got a security advisory. An app left alone for a year has taken on debt anyway, and a fresh install is the first test.

That is what code rot means in practice: the code stays the same while the world it runs in changes. Runtimes publish their support windows. On the Node.js release schedule, version 20 is marked EOL and versions 22 and 24 are LTS, and the page says production applications should only use Active LTS or Maintenance LTS releases. An app deployed on Node.js 20 and never touched since has moved from a supported runtime to an unsupported one without a single commit. Providers retire old API versions on their own timetable, and security advisories land on packages that were fine the year before.

A certificate shows the mechanism with dates attached. Let’s Encrypt’s note on the DST Root CA X3 expiration said DST Root CA X3, the older root certificate behind its cross-signature, would expire on September 30, 2021, so older devices that do not trust its own newer ISRG Root X1 would start getting certificate warnings on sites using Let’s Encrypt certificates, with one exception for older Android devices. For API providers it set two conditions: every client must trust ISRG Root X1, and OpenSSL clients must use version 1.1.0 or later, because in OpenSSL 1.0.x “a quirk in certificate verification” means that even clients that trust ISRG Root X1 would fail on the default chain. An update dated September 30, 2021 says the cross-sign expired as planned, and one dated February 5, 2024 says the Android compatibility cross-sign is close to expiring. Code nobody changed still breaks when something it depends on reaches an end date, and the owner who tracks those dates pays the debt on schedule instead of during an outage.

The signs an owner can see without reading code: a fresh install fails, the build prints warnings about deprecated packages, or a provider emails about an API version being switched off.

Maintainability: the property that debt wears down

Maintainability is how cheaply and safely software can be changed. ISO/IEC 25010 treats it as a quality characteristic with five parts: modularity, reusability, analysability, modifiability and testability. Tech debt is what wears it down, one shortcut at a time, until each change costs more than the last.

Outside software, maintainability means how easily equipment or a product can be repaired. In software it is about change. ISO/IEC 25010’s quality model lists nine quality characteristics and defines maintainability as “the degree of effectiveness and efficiency with which a product or system can be modified to improve it, correct it or adapt it to changes in environment, and in requirements”. In plain words, software maintainability is how much it costs to make the next change safely.

Per-file scores can look healthy while the app as a whole resists change, and that gap is the subject of why an AI-built app gets harder to change. The audit averages for maintainability against reliability sit in the AI-code article the next section points to.

Why it matters for an app you did not write

If you own an app someone else wrote, or one an AI assistant wrote for you, you hold a loan without the paperwork. Nobody wrote down which shortcuts were taken or why, so the debt has to be found before anyone can price it.

In practice the interest shows up as three things a founder can see without opening the code: estimates that keep growing for changes of the same size, fixes that break something in another part of the app, and one person who is the only one able to deploy. Each of those is a cost you already pay, whether or not anyone has called it debt.

The audit findings on maintainability against reliability in AI-built apps, and how to measure and pay down debt that an AI wrote, are in technical debt in AI-generated code. Taking over a codebase someone else wrote is its own job with its own order of work: legacy code takeover. If a raise is coming, the same debt matters to the investor’s technical reviewer, which raises the question of what investors look for in code.

How it works: measuring it, carrying it, and paying it down

The four parts below follow the order an owner meets the questions: how much debt there is, what to do about it, who schedules the work, and when taking on more is fine. The audit figures on this page come from my audits, with their denominators; every other fact comes from the source named beside it, and the rest is labeled as my own rule or view.

How to measure tech debt without reading the code

Tech debt has no single unit, so measure six proxies and watch the trend: tests that run and pass, type-check errors, lint warnings, outdated or vulnerable dependencies, deploys that needed a follow-up fix, and whether the README gets a clean checkout running. Tools report a debt ratio too, useful only compared with last month.

No single process or set of tools can measure technical debt in one unit, because debt is a cost to work that has not happened yet. SonarQube’s metric definitions show how technical debt is calculated in that tool: the debt is the sum of the estimated minutes to fix each maintainability issue, and the debt ratio divides it by the estimated cost to develop the code, with a default of 30 minutes per line that can be changed. Both figures rest on the tool’s own rules and estimates, so I’d use them for the trend in the same tool, never for a comparison with someone else’s app.

The six numbers below need no code reading, only a developer’s help for about an hour. I set the “bad value” column myself.

The numberHow to get itWhat a bad value looks like
Automated tests that run and passThe test command’s summary lineNo tests, or failing tests that stay failing
Type-check errorstsc --noEmit on the app’s own tsconfig, or tsc -b where the root tsconfig only holds referencesAny errors on the main branch, or the check switched off in the build
Lint warningsThe project’s lint commandA count nobody reads, or one that grows every month
Outdated and vulnerable dependenciesnpm outdated for versions, npm audit for known vulnerabilities (by default it needs a package-lock)Known vulnerabilities left open, or packages several major versions behind
Deploys out of the last ten that needed a follow-up fixThe host’s deploy list: count the rollbacks and hotfixesA follow-up fix after deploys as a habit, not an exception
A README that starts the app from a fresh checkoutFollow it from a fresh cloneIt fails before the app starts, or only one person knows the missing step

The fifth number is a small version of what DORA calls change fail rate, “the ratio of deployments that require immediate intervention following a deployment”; DORA’s definition of change failure rate adds that this is likely to mean a rollback or a hotfix.

To track tech debt, keep the six numbers as a dated row in the repository; I aim for a new row roughly once a month. A technical debt assessment by a person adds what the numbers cannot: which of the debt sits on the money, login and data paths, where a mistake costs the most. The tools and thresholds behind each number are in code quality checks. Repository signals particular to AI-written code, such as file size, churn and repeated names, belong to the AI-code article.

How to reduce tech debt, and how to manage what stays

Tech debt is reduced by paying the interest you are about to pay: before changing an area, add a test around it, delete what is dead there, and tighten its types, then make the change. Whatever you leave goes in a short register: the debt, where it sits, what it costs each change, and the trigger for fixing it.

That is my rule. It reduces technical debt where the next change is headed anyway, so the paydown and the feature share one round of testing. Each kind of debt has its own fix and its own method:

The debtWhat paying it off looks likeWhere the method is
Missing tests on the critical pathsA few end-to-end smoke tests on signup, login, the core action and payment, run on every changehow to write end-to-end smoke tests
Dead and duplicated codeDelete what nothing calls; one shared function where there were copieshow to find unused code in a repo
Loose typesStrict mode switched on, errors fixed instead of suppressedhow to enable TypeScript strict mode
Outdated dependenciesUpgrades in small steps, tests run after eachThe fourth number in the measuring table
A README nobody can followRewritten from a clean clone until every step worksThe sixth number in the measuring table

To clean up a whole app rather than one area at a time, the order of the work matters, and how to clean up a vibe-coded app sets it out.

To manage technical debt you decide to keep, write it in a register with four columns: the debt, where it sits, the interest it charges each change, and the trigger for fixing it. I’d review that register about once a month. A register is how a small team can handle technical debt it cannot pay yet without forgetting it. Where debt is acceptable and where it is not is answered in the AI-code article’s FAQ.

Technical debt in Scrum and in product management: who owns the backlog line

Technical debt in Scrum is ordinary Product Backlog work: the Product Owner orders it with everything else, and the Definition of Done is the guard against adding more. Teams make room in three ways: a fixed share of each iteration, a debt item attached to each feature that touches the area, or a whole iteration spent on debt.

The Scrum Guide makes the Product Owner accountable for ordering Product Backlog items and calls the Product Backlog “the single source of work undertaken by the Scrum Team”. Its Definition of Done is “a formal description of the state of the Increment when it meets the quality measures required for the product”, and an item that does not meet it cannot be released. The guide does not use the term technical debt at all, so debt work is backlog work like any other.

The three ways to make room are my suggestion. A fixed share of every iteration is the easiest to keep going. A technical debt sprint, a whole Scrum iteration spent on debt, is the easiest to cancel when a deadline moves.

For a product manager, the case for debt work is made in the interest, not the principal: the days the last change in that area took, set against the days it would have taken in clean code. In project management usage, technical debt means the same thing with a budget line attached.

Technical debt at a startup: when borrowing is the right call

Technical debt at a startup is often the right loan: before anyone pays, learning fast beats tidy code, and much of what is written gets replaced. The kind never worth taking is the kind that can lose data, money or accounts. Start repaying at the first paying customer, the first engineer hired, or due diligence.

That is a judgment of mine, not a rule anyone enforces. Before the first payment most code is an experiment, and an experiment that gets thrown away never needs paying back. The exception is anything where a mistake lands on customers instead of on the next change: a missing backup, a payment flow nobody checked, access rules that let one account read another’s data. Those are not shortcuts worth taking at any stage.

How to check your own app: six numbers in an hour

A founder can size tech debt in an hour with six numbers: tests that ran and passed, type-check errors, lint warnings, outdated and vulnerable packages, follow-up fixes in the last ten deploys, and whether the README works from a fresh clone. Write them down with the date; a paydown worked when a number moved and nothing broke.

Start in the browser. On GitHub, open the repository’s Actions tab and check whether anything runs on a pull request. Then open the Security and quality tab and count the open Dependabot alerts. If there are none because alerts are off, a repository administrator can switch them on in the repository’s Settings, under the Security and quality section’s Advanced Security page.

The rest needs a terminal, or a developer for about an hour. The commands hold for a Node and TypeScript repository; other stacks use their own test, type and audit commands.

  1. 01 Run the test command. Record how many tests ran and how many passed. Evidence: the summary line.
  2. 02 Run the type check and record the error count: tsc --noEmit against the app's own tsconfig, or tsc -b where the root tsconfig only lists references, as Vite's React and TypeScript template does. Evidence: the last line of output.
  3. 03 Run the linter and record the warning count. Evidence: the summary line.
  4. 04 Run npm outdated and npm audit. Record how many packages are outdated and how many vulnerabilities are reported; by default npm audit needs a package-lock file. Evidence: both summaries.
  5. 05 From the host's deploy list, count how many of the last ten deploys needed a follow-up fix. Evidence: the dates of those deploys.
  6. 06 Follow the README from a clean clone and note the first step that fails. Evidence: that step, or a note that none failed.

On Vite’s template, the root tsconfig.json lists no files of its own, only references, so a root tsc --noEmit has nothing to check; tsc -b follows the references to the configs that hold the app’s code.

Write the six numbers in one dated row and repeat on the monthly rhythm from the measuring section. Nothing broke means the smoke tests still pass after the paydown, and a number that moved is the evidence the work was worth doing.

Where the sprint fits

The Production Hardening Sprint’s area 10, code health and AI development guardrails, covers the kinds of debt on this page, each in the published wording: 10.1, “Remove dead code and consolidate duplicated logic while preserving required behavior” ; 10.3, “Define types at every API boundary and share contracts between frontend and backend using the stack’s appropriate tooling” ; 10.4, “Organize the codebase consistently and explain its layout in the README” ; 10.5, “Automate smoke tests for signup, login, the core product action, and payment flows”, which we verify this way: “Run the suite in CI and demonstrate that an intentional regression fails it” ; and 10.6, “Enable TypeScript strict mode and resolve errors in TypeScript applications, with an equivalent strict-checking approach for other supported stacks” . Outside this sprint: building new product features or modules, completing unfinished core features or business workflows, and rebuilding core functionality that does not yet perform its intended job; new features and completing unfinished core workflows are separate work. The app’s current framework and hosting setup are the starting point; components are refactored or replaced where the production work requires it. Every deliverable is listed in the published scope.

Common questions about technical debt and code rot

Is tech debt a bug?

No. A bug is behavior that is wrong now; tech debt is code that works today and makes the next change slower or riskier. Debt raises the odds of bugs, and a bug that costs too much to fix and gets worked around can turn into debt.

What is another name for technical debt?

Tech debt is the usual short form, and IBM’s and Confluent’s guides also call it code debt, with IBM adding design debt. “Technological debt” and “technology debt” are spelling variants, not separate terms.

Can code rot be fixed?

Yes. Upgrade the runtime and the dependencies in small steps behind smoke tests, then keep automated dependency updates switched on; on GitHub, Dependabot version updates are “automated pull requests that keep your dependencies updated, even when they don’t have any vulnerabilities”. Put the runtime’s support status from its release schedule in the debt register, so the next end date is planned work.

What is the difference between reliability and maintainability?

In ISO/IEC 25010 terms, reliability is the “degree to which a system, product or component performs specified functions under specified conditions for a specified period of time”, while maintainability is how effectively and efficiently it can be modified. An app can be easy to change and still fail often, and an app that rarely fails can be very hard to change; the audit averages for both are in the AI-code article.