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 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, 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 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.
Fig. 1 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 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.

BugTechnical debt
Who notices firstUsers, testers, monitoringThe engineers who change the code
What is wrongBehaviour does not match what was specifiedThe structure makes the next change slower or riskier
How you find itBug reports, failed tests, incidentsHotspots, slow lead times, survey answers, static analysis
How you rank itSeverity and how many users it affectsInterest paid now, and how often the code changes
When it is fixedThe behaviour is correctThe next change in that area is cheaper

The two feed each other. In the Code Red study of 39 proprietary codebases, 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, 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.

RecklessPrudent
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. 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: 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.

KindExampleThe interest you pay
CodeA 2,000-line pricing module that most changes pass throughEach change takes longer and breaks something nearby
ArchitectureTwo services that must be released together; many modules writing one shared tableChanges need coordination; no part can be scaled or replaced alone
TestsNo automated tests around invoicing; a flaky end-to-end suiteManual 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 1No security patches; libraries drop support; upgrades arrive as emergencies
Infrastructure and releaseHand-configured servers; a manual, multi-hour deploymentBigger, riskier releases and slower recovery
Documentation and knowledgeOne person understands the nightly batchSlow onboarding; incidents wait for that person
DataOverloaded columns; a pipeline of ad hoc scripts feeding reportsEvery report needs cleanup; one input change ripples everywhere
Dead code and stale flagsAn unused function left in a production system; flags nobody removedMore 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 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.
Fig. 2 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 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.

SignalWhat it showsWhere the data comes fromWhat it cannot tell you
DORA delivery metricsWhether changes to one service are slowing down or failing more oftenDeployment pipeline and incident recordsWhy: debt, staffing and process problems look the same
HotspotsWhere change effort concentrates in complex codeVersion control history and a size measureWhether the complexity is essential, or debt in code nobody changes
Dependency age and end-of-life datesHow far libraries and runtimes lag behind supported versionsLock files, manifests and vendors' support pagesWhether a particular old version is actually risky
Static analysisRule violations and an estimate of the effort to fix themSonarQube or a similar analyzerThe interest, and any problem its rules do not detect
Engineer surveyWhich kinds of debt slow people down, and whereA short survey each quarterEarly warning: debt shows up only once it hinders people

Delivery signals: the DORA metrics

DORA, 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 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 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.
Fig. 3 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 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:

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 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 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 covers keeping dependencies under control.

For one number, libyear 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, 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 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.

ItemSymptomInterest paid nowCost to fixOwnerTrigger to act
Pricing rules module (pricing/rules.py)Every pricing change needs a manual regression pass; 2 of the last 6 incidents started hereAbout 2 extra days on each of 3 pricing changes a month3 weeks for 2 engineers, tests firstCheckout leadBefore the Q1 pricing project starts
API runtime on Node.js 20Past end of life since 30 April 2026; new library releases need Node.js 22 or laterNo security patches; library upgrades blockedAbout 1 week to move to Node.js 24 LTS and fix failing testsPlatform engineerNow

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 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 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.
Fig. 4 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 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

ApproachFits whenMain risk
Refactor in placeThe design is mostly sound, and tests exist or can be addedSlow progress if the hotspot is very large
Replace piece by piece (strangler fig)The system works, but its framework, runtime or structure blocks changeRunning old and new side by side for a while
Rewrite from scratchThe system is small, or its behaviour is simple and fully specifiedMonths 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 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, and many popular packages drop support for it over time. The vendors' dates as of 9 October 2026:

PlatformVersionStatus on 9 October 2026Support ends
Node.js20End of life30 April 2026
Node.js22Maintenance LTS30 April 2027
Node.js24Active LTS, maintenance from 20 October 202630 April 2028
Node.js26Current, LTS from 28 October 202630 April 2029
.NET8 (LTS), 9 (STS)Maintenance10 November 2026
.NET10 (LTS)Active14 November 2028
Python3.10End of life1 October 2026
Python3.11, 3.12, 3.13Security fixes onlyOctober 2027, October 2028, October 2029
Python3.14Bug fixesOctober 2030
Oracle JDK8, 11Extended SupportDecember 2030, January 2032
Oracle JDK17Premier Support listed to September 2026 (or later); Extended Support fee waived from October 2026Extended Support until September 2029 (or later)
Oracle JDK21, 25Premier SupportExtended Support until September 2031, September 2033 (or later)

The dates come from the Node.js releases page and release schedule, the .NET support policy, the status of Python versions and the Oracle Java SE support roadmap. 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.
Fig. 5 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 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 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 reads its settings from .github/dependabot.yml 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:

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, 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 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 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.
Fig. 6 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 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:

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 requestExample
The itemThe pricing rules module
Interest now, with evidenceAbout 2 extra days on each of 3 pricing changes a month; 2 of the last 6 incidents started there
What the team will doAdd tests around current behaviour, then split the module; no feature changes
Cost3 weeks for 2 engineers this quarter
What should changePricing changes back to about 4 days, and fewer hotfixes for the service
When you will checkAt the end of next quarter, from the register and the DORA numbers
Risk of waitingThe 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, 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.
Fig. 7 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 twoUsual causeFix
The prototype is now the product, and every feature fights itIts expiry passed without a decisionDecide now: replace it piece by piece, or keep it and record the debt
A scanner flags the runtimeThe runtime the system launched on is near or past end of lifeUpgrade on a schedule, with the date in the register
Releases slow down and hotfixes riseDebt in the busiest modules, and tests that never got writtenThe hotspot list, tests first, reserved capacity
Old flags and branches that nobody dares to deleteNo removal task or expiry when they were addedAn expiry test, and a removal task with every flag
Two versions of the same thing in productionA migration abandoned partway, one of Google's 10 categoriesFinish it or roll it back; do not keep both
Only one person can change the billing codeKnowledge never written down, then staff turnoverPairing, 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 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.