Maintenance and Efficiency

Technical Debt: Seeing the Cost of Future Change

How to tell technical debt apart from defects, outdated style and personal taste, describe it as a verifiable problem with evidence, and decide whether…

Chapter 66Maintenance and Efficiency

A working system can deliver value and, at the same time, make the next change harder. Say a team runs an import by hand because there is not much data yet; later, every new file format needs its own instructions, and support spends hours on it. The fact that a system is imperfect does not, on its own, make something technical debt. You need to find the specific trade-off or constraint, show its consequence, and decide whether it is worth changing now.

Learning outcome. The reader can distinguish technical debt from a defect, an outdated style or a personal preference; describe its symptom, cause, impact and evidence; weigh the options against the product's context; and make a reasoned decision to pay down part of the debt, reduce it, monitor it, or leave it unchanged for now.

The metaphor explains costs but does not calculate them

Technical debt is a metaphor for an early technical compromise, or an accumulated constraint, that brings a benefit now but makes future change more expensive or riskier. Ward Cunningham used it to explain why an immature solution can help ship a product sooner, while leaving it unconsolidated eventually gets in the way of developing it. He compared the extra effort needed to change such a solution to the interest paid on a loan. Cunningham, WyCash report

Within the metaphor you can distinguish:

Financial metaphor In a software product
Principal The amount of work needed to remove or contain a specific technical constraint
Interest The extra time, repeated defects, manual steps or risk incurred during later changes in that area
Benefit of the loan The time or other value gained thanks to a decision that simplified or postponed work now
Repayment A bounded improvement, a gradual replacement, removal of an unneeded feature, or elimination of the cause

This is not a balance sheet. There is no reliable universal "interest rate" for any item that you could derive from the number of lines, style violations or the age of the code. Estimates of time and future benefit are approximate; assumptions, future demand and priorities can all change. Even the principal is not always a single one-off sum: a necessary replacement may be completed over several releases. Martin Fowler explicitly warns that the loan metaphor has limits: unlike a bank loan, software debt often "accrues interest" when the team has to change that particular area. So stable code that the product no longer touches may not need repaying at all. Fowler, "Technical Debt"

Do not turn this into an excuse for carelessness: "we'll clean it up some day", with no reason, no owner and no condition for review, is not a deliberate compromise. And do not confuse aesthetic dissatisfaction with proven costs: "I prefer the new framework" says nothing yet about any benefit to the product.

Separate a compromise, a mistake and a matter of taste

A decision about debt starts with classifying the problem. Different causes call for different responses.

Situation What it means How to handle it
Known temporary compromise The team simplified a solution for a specific near-term benefit and understands the future cost Record the benefit, the constraint, the person responsible and the condition for review
Constraint noticed later New requirements or experience showed that an earlier model or structure is getting in the way Measure its current impact; do not blame the earlier team with hindsight
Defect Actual behaviour violates a requirement or a contract Fix the defect with a priority set by its consequence; separately check whether the system's structure is a recurring cause
Outdated style or tool A newer version or fashion exists, but no impact on the product has been shown Check support, compatibility, risks and future needs; do not change things purely for novelty
Reviewer's taste Someone dislikes a name, a format or an architectural direction without any noticeable harm Treat it as a suggestion until an example of cost or risk is given

Fowler suggests considering whether a decision was deliberate separately from whether it was prudent: a team might have taken on a risk knowingly, inherited it unknowingly, or discovered a better approach only after working with the product. This is a useful lens for discussion, not a standard for grading decisions or for automatically accepting every compromise. In particular, a team calling a decision "temporary" does not make it prudent: the benefit may have been vague and the costs underestimated. Fowler, "Technical Debt Quadrant"

A defect and debt can be related, but they should not be merged into one task without a reason. If the total on an order is wrong, that is a defect to be fixed. If the error keeps recurring because the pricing rules have been copied into several places, the duplication may be a separate technical constraint that raises the risk of new defects. The first question is "does the system work correctly?"; the second is "does its structure make the next change unjustifiably difficult or dangerous?"

Name the consequence, not just an uncomfortable feeling

A technical constraint matters in practice if it creates a recurring cost, reduces reliability or blocks a product need. For example:

  • changing one rule requires editing several copies in step;
  • tests are flaky or do not check important consequences, so the team spends time on manual verification;
  • a release needs repeated manual steps that are easy to skip;
  • a single operational mistake can corrupt data or stop a critical function;
  • contracts between components depend on hidden agreements and break often;
  • knowledge of deployment, integration or recovery is concentrated in one person;
  • an external dependency cannot be updated within the required support or compatibility window.

Unpleasant code may have no such consequence if it is stable and is not part of future work. Conversely, a short manual step can be significant debt if it regularly delays a release or loses data when it is performed. If a problem creates a direct risk to security, confidentiality or data integrity, do not bury it in an open-ended "tech debt" list: it needs a risk owner, a review deadline and a temporary safeguard while the team prepares the changes.

Do not blame tools for the complexity of the problem itself

Fred Brooks distinguished essential complexity, the work tied to the rules and concepts of the problem domain itself, from accidental complexity, which is added by the means of implementation, the language, the tools or the constraints of the environment. His distinction is not a classifier for technical debt: it helps you see which part of the difficulty a better tool or better organisation can reduce, and which part comes from the problem itself. Brooks, "No Silver Bullet", University of North Carolina technical report, 1986

A new framework may remove needless repetition or simplify execution; it will not decide for the product team what happens with a partial refund, how to round currency, or who may change an order after it has shipped. An automated agent can help investigate duplication in the code, but it will not establish which pricing rule the business approved. If the rule itself is unclear, resolve the ambiguity in the requirements first. A rewrite will not turn the unknown into the known.

Likewise, no architecture is debt by name alone. A monolith can be simple and suit the needs of a small product; splitting into services can add network calls, contract management and operational work. As load, the team or product requirements grow, that trade-off may stop being acceptable. Software Architecture: The Hard Parts emphasises analysing options in a specific context: the same division into components shifts the balance between autonomy, coordination, coupling and maintenance cost. The authors warn against choosing a "winner" merely by counting abstract pros and cons. O'Reilly: Software Architecture: The Hard Parts; the authors' discussion at Thoughtworks

Real systems change the cost of a compromise

The experience of the Dodo Pizza restaurant chain, described on the Agile Alliance site, shows why decisions have to be judged against load and the delivery process. A separate early episode is described by a Dodo Engineering author on Habr: a popular post by the chief executive caused the blog to query the main database for the revenue of the whole chain every time, and as a result the restaurants' tills stopped getting responses. The team quickly added a read-only database replica, but had no plan for a technical rethink. During an advertising campaign in 2018, load grew from roughly 100 to 300 orders per minute; the system hit the limits of vertical scaling and the database stopped processing requests. Before that, the product had grown quickly, but there were few automated checks and manual regression testing lengthened releases.

The team halted most product development in favour of technical changes. They planned for three months, but the work took about a year; the report's author notes that during that time the teams shipped one significant business feature. They automated deployment, gradually separated out the most heavily loaded component, added an event queue and invested in automated and performance testing. Despite a year of work, during the next large campaign the system again went down and restarted every three hours; the author links the crisis to large business losses. The team continued working on asynchronous processing, load testing and logging; the report describes better stability in 2019.

This is the experience of one product, not proof that monoliths are debt in themselves or that a year-long halt to development suits other teams. It shows the cost of postponing work under a particular kind of growth, and also the limits of a big "repayment": changing the structure did not guarantee resilience to new load, so the team needed separate performance data and changes to its process. The monetary estimate of more than 1.5 million dollars comes from the report's author; read it as his estimate of the costs, not as an independent audit. Agile Alliance: "Technical debt leading to a company crisis"; Dodo Engineering: the original publication of the report on Habr

Shopify described a different path for its large monolith: gradually creating boundaries between components and their contracts while keeping the system a monolith. An earlier tool collected, during tests in the continuous integration (CI) environment, a graph of actual method calls between components. That data was noisy, and a full analysis took more than an hour. Packwerk, a static dependency analyser, ran in minutes and, at the time the article was published, was already integrated into the pull-request process: it rejected changes that violated the dependency graph or the encapsulation of components. Shopify was then applying Packwerk to roughly a third of the components in the main monolith, so the checks did not yet cover all of the code. The company also described a trade-off between temporarily grouping responsibilities under assigned owners and fully separated components, which are easier to understand but can leave the rest of the system as tangled as before. This is the company's own engineering report about its own context, not a universal recommendation. It shows that gradually improving a monolith can make more sense than immediately splitting it into services. Shopify Engineering: "Under Deconstruction: The State of Shopify's Monolith"

Describe debt as a verifiable problem

A note saying "rewrite the module nicely" does not help anyone plan the work. Another person does not know what hurts, whom it gets in the way of, what an acceptable result looks like, or when to come back to the question. Describe the specific situation instead:

Field Question
Symptom What regularly slows down, breaks or needs manual work?
Cause Which decision, duplication, dependency or missing process causes it? What is known, and what is only assumed?
Impact and evidence Which changes, incidents, repeated errors, steps or delays confirm it?
Owner Who is responsible for the area and for reviewing the decision?
Options Can we accept the risk, simplify locally, replace a part, remove what is not needed, or change the process?
Cost and risk of change What will each option affect in terms of compatibility, data, users, tests, releases and recovery?
Review trigger Which change in demand, load, support or risk will show that it is time to revisit the question?

Do not invent a precise cause when there is no evidence yet. Issue #14010 for DefectDojo described a regression after version 2.53.0: previously the search checked all matching findings, but after the change it considered only the first match. If that first record had already been mitigated, the duplicate-marking function stopped with an error and did not move on to the next candidate. Duplicate issue #14012 added a reproduction and a log in which the system found 30 matches but still left the new record as a separate active finding. Its author suggested this was a consequence of a refactoring; the page presents that as a possible cause, not an established chain of historical changes. This was a defect, not optional debt: pull request (PR) #14011 fixed the iteration over candidates and was merged into the bugfix branch on 4 January 2026; the PR itself notes that the fix does not apply to Pro, where there was separate work. DefectDojo Issue #14010; duplicate #14012; PR #14011

Compare risk with future work

What matters for prioritising debt is not its age or the number of style violations, but its consequence in the context of the product's plans. Ask:

  • how often the team already changes this area, and whether the roadmap will bring it back there;
  • how much time, repeated checking or manual work the constraint adds;
  • which errors may arise, and whom or what they will affect;
  • whether it concerns security, data access, financial results or the integrity of records;
  • what the cost and risk of the intervention itself are, including compatibility, migration and recovery;
  • how long the product is likely to live, and which other changes might alter the assumptions.

A critical data risk may demand immediate containment or a fix, even if the code rarely changes. Low impact in a feature scheduled for removal in a month can make a full replacement unjustifiable. A rarely changed module is not worth rebuilding just because it is ugly, but it is dangerous to ignore if it contains an active vulnerability or blocks a mandatory migration.

Weigh the cost of removal, the risk of the change, the future costs avoided and other planned work together. Do not reduce all of these factors to a universal "how much debt" score: two departments can rate the same item differently because of different change frequency, cost of error and product horizon. A decision to defer must also include a reason and a date or event for the next review.

Choose the scope of action and how you will check the benefit

Repayment is not limited to a big rewrite. Depending on the evidence, a team can:

  1. Accept and monitor. Record the reason, the owner, a temporary control and the trigger for returning to the question.
  2. Simplify locally. Remove unnecessary steps or duplication where the team is already working, if that genuinely lowers future costs.
  3. Replace gradually. Change one boundary or part of the system at a time when compatibility and data do not allow a safe one-shot replacement.
  4. Remove what is unused. Measure whether a feature is really needed, then remove it together with the support burden it carries.
  5. Change the process. Add an automated check, a contract owner, code generation or another repeatable agreement if the problem does not come only from the code.
  6. Rethink a significant part. Compare this option with local improvements on the evidence. A full rewrite is an expensive hypothesis with new transition errors and unknowns, not a synonym for "paying off the debt".

Before the change, define what should improve: less time on a typical request, fewer regressions, one manual step instead of several, a shorter change review, or predictable recovery. After the change, check that specific result and its side effects. For an architectural choice, record the decision and its review condition in an architecture decision record (ADR); track the concrete work in a backlog, and the impact on delivery in the release plan.

Example 1: the manual import stopped being cheap (hypothetical scenario)

At first the product accepts a small number of spreadsheet files from a few partners. An administrator checks the format by hand and runs the import once a week. At this stage an automated pipeline with recovery and reprocessing could cost more than the process itself.

As the number of partners grows, support runs the import several times a day and fixes incomplete rows differently each time. The team records the time spent on manual processing, the repeat tickets caused by rejected rows, and the cases where a rerun creates duplicates. A bounded task follows from these observations: add a format check before data is saved, and make re-importing safe for records already processed; keep the existing recovery system rather than rewriting the whole pipeline.

The import owner agrees the criteria: support no longer edits acceptable files by hand, the reason for a rejection can be traced to a specific row, and re-importing does not create another record. As an illustration of a possible outcome, not as actual data: in the four weeks after release, manual work fell from four hours to 45 minutes a week, and re-imports created no duplicates in the runs checked. The team also checks for false rejections and for recovery after a failure. If there were few partners, imports were rare and a mistaken run was easy to undo, it would have been better to postpone the automation or keep it simpler.

Example 2: an agent found a newer library (hypothetical scenario)

An agent notices that the project uses an older version of a library and suggests replacing it with a newer one because it is "more modern". That is not yet evidence of technical debt. The team checks whether the current version is still supported, whether a needed feature is missing, whether there is a real compatibility risk, and which tests would confirm the behaviour after a switch.

It turns out that the current version is supported, the system calls it only from a rarely changed internal tool, and the newer version would require changes to configuration and dependent modules. The team leaves the version as it is, adds the version's end-of-support date to the record as a review trigger, and does not take on the risk of switching now. Hypothetical result of the quarterly review: support is still ongoing, no needed feature is missing, and the library has caused no incidents, so the team has not spent time on a migration with no noticeable benefit. If the check finds an unsupported version with a known active vulnerability, or one that blocks a required upgrade, the decision changes; at that point it is a concrete risk, not a preference for novelty.

An agent is useful for finding differences and proposing options, but its conclusion must refer to the specific version, the way it is used, the source of risk and the scope of changes. Do not let a task to "update the library" automatically grow into replacing the whole data-access layer. Nor should a separate defect or an urgent security fix be weighed down with an optional rewrite of neighbouring modules.

Example 3: copies of a pricing rule produced different totals (hypothetical scenario)

A hypothetical shop applies a discount both in the basket and when it creates the invoice. After the rule changes, the customer sees one amount at checkout and a different one on the invoice. The team fixes the wrong amount as a defect. A look at the change history shows that the same rules are implemented in two places and have twice caused similar errors; that is now evidence of a separate structural constraint.

Instead of a general demand to "rewrite the basket calculation", the team limits the follow-up work to a single source for the rule, with verification criteria: the same set of items gives the same price in the basket and on the invoice; edge-case discount values do not change the result between screens; the log shows which version of the rule was applied. Hypothetical result for the first month after release: a sample of orders showed no discrepancies between basket and invoice, and the next rule approval needed one change in a single place instead of synchronising two copies. The team keeps watching both indicators and any side effects. If the two code paths implement different rules that are both in force, and that is intentional, they should not be merged just because they look alike.

How to change such an implementation safely is a separate topic, covered in Refactoring. Here the important thing is to prove the problem first and bound the goal, so that a useful defect fix does not turn into an unplanned rebuild of the whole shop.

Track the cost, not a decorative score

Useful observations depend on the problem. For an import: minutes of manual work and the percentage of files that need reworking. For a testing constraint: the duration of the checks, flaky runs and regressions. For an operational risk: failed recoveries, intervention steps and incidents. For changing component boundaries: how many components change together in typical tasks.

Compare similar changes over the same period, and account for other causes: seasonal load, changes in team composition, new requirements, new checks. A shorter release time after a rebuild may coincide with the team gaining experience or with a change in process, so the number alone does not prove causation. Tracking exists to help you learn and choose the next step, not to report invented precision.

Record the decision and revisit it when a condition is met

For a long-lived compromise, record in an ADR the context, the decision drivers, the decision taken, the known consequences, and the event that should trigger a review. For example: "Manual import is acceptable as long as it runs no more than once a week and takes up to 30 minutes; if the number of partners grows or the incident recurs, the team assesses automation." This is not a commitment to start a rewrite automatically; it is a way of noticing that the original conditions have changed.

A short local item can live in the backlog next to the features it relates to. When planning changes in that area, the team can pay down part of the debt if doing so is safe and justified by the upcoming work. After release, check the success criteria, compatibility, data and operational behaviour. If you decided not to pay for now, keep the decision's owner and the date or condition for review; silence in the task list is not a decision.

Sources and further reading

Pages and technical reports were checked on 25 September 2026; the Agile Alliance report and its original publication by Dodo Engineering on Habr were re-checked on 10 October 2026. For historical material the year of publication is given separately; the quantitative results of Dodo Pizza's experience are presented as the report author's testimony, not as an independent verification.