ArchitectMind
  1. Home
  2. Tutorials
  3. Book 8 · Architecture Quality Attributes
  4. Volume VI
  5. Technical Debt
Book 8 · Volume VI — Maintainability, Modifiability & Testability · Topic 1.2

Technical Debt

Shortcuts that charge interest on every future change

11 min readStage 3 · Systems10 interview questions
Chapter
Chapter 1 — Maintainability
Prerequisites
Topic 1.1
You will be able to
Explain technical debt with principal and interest, classify debt by intent and kind, find the debt that actually charges interest, keep a useful debt register, decide when to take on debt deliberately, and plan repayment that competes fairly with features
Covers
Technical debt
On this page
  1. 1. Concept / Definition
  2. 2. Why It Exists
  3. 3. Core Concepts — Technical Debt
  4. 4. How It Works Internally — Finding the Interest
  5. 4A. Deep Dive — Deliberate Debt Done Right
  6. 5. APIs / Features / Syntax
  7. 6. Practical Examples
  8. 7. Common Problems & Mistakes
  9. 8. Performance & Production Considerations
  10. 9. Architecture & Design Considerations
  11. 10. Interview Questions & Answers

1Concept / Definition

Technical debt is the implied future cost of choosing an expedient solution now instead of a better one. Like financial debt it has principal (the cost to fix it) and interest (the extra cost every change pays while it exists). Debt is not always bad — taken deliberately and repaid, it can buy speed. Unmanaged, it compounds until change becomes prohibitively expensive.

Principal and interest
Figure 1 — Principal and interest
TermMeaningExample
PrincipalEffort to remove the debt8 days to consolidate pricing logic
InterestRecurring cost while debt existsEvery pricing change touches 4 places; 2 bugs/quarter
Interest rateHow often the debt area changes × pain per changeHigh for hot code, near zero for dormant code
DefaultDebt so large a rewrite seems the only option"Legacy admin must be rebuilt"
Table 1 — Vocabulary
Debt vocabularyMeaning
HotspotCode that is both complex and frequently changed
Change couplingFiles that repeatedly change together
Boy-scout ruleLeave code better than you found it
Debt registerTracked list of known debt with owner and interest
StranglerReplace a legacy part incrementally behind an interface
Table 2 — Terms

2Why It Exists

CauseExample
Deadline pressure"Hard-code the tax table for the sale; parameterise later"
LearningThe domain model was wrong because the domain wasn't understood yet
EntropyMany small changes without refactoring
Ageing dependenciesFramework versions reach end of life
Skill gapsPatterns misapplied
Table 3 — Where debt comes from
Hidden vs managed debt
Figure 2 — Hidden vs managed debt

3Core Concepts — Technical Debt

Technical debt quadrant (Fowler)
Figure 3 — Technical debt quadrant (Fowler)
QuadrantResponse
Deliberate & prudentRecord it, set a repayment trigger
Deliberate & recklessChallenge in design review
Inadvertent & prudentNormal learning — refactor as understanding improves
Inadvertent & recklessSkills, reviews, templates
Table 4 — How to respond per quadrant
Kinds of technical debt
Figure 4 — Kinds of technical debt

4How It Works Internally — Finding the Interest

SignalSource
Hotspots: churn × complexityGit history + static analysis (CodeScene-style)
Defect density per moduleIssue tracker linked to code
Lead time per modulePR data
Change coupling (files that always change together)Git log analysis
Developer painSurveys, retros, on-call notes
Table 5 — Evidence, not opinion
BASH
# Churn per file in the last 6 months (hotspot input)
git log --since="6 months ago" --name-only --pretty=format: -- src/main/java \
  | grep -v '^$' | sort | uniq -c | sort -rn | head -20

4ADeep Dive — Deliberate Debt Done Right

StepExample (flash sale)
Decide explicitlyADR: hard-coded sale pricing rules for 2 weeks
Contain itIsolated in SalePricingOverride behind a flag
RecordDebt register entry with owner
TriggerRepay in sprint after sale
VerifyFlag removed, code deleted, entry closed
Table 6 — Deliberate prudent debt
Debt register (excerpt)
Figure 5 — Debt register (excerpt)

5APIs / Features / Syntax

YAML
# Debt register entry (kept in repo: docs/debt/pricing-duplication.yaml)
id: DEBT-042
title: Pricing logic duplicated in cart, checkout, order, invoice
kind: design
owner: pricing-team
interest:
  change_frequency: weekly
  defects_last_quarter: 2
  extra_effort_per_change: "~0.5 day"
principal: "8 days"
trigger: "before next promotions feature"
status: planned
JAVA
// Paying it down: one PricingService used everywhere (duplicates delegated, then deleted)
@Deprecated(forRemoval = true)   // DEBT-042: remove after all callers migrate
public Money legacyCartPrice(Cart cart) {
    return pricing.priceOf(cart.toPricingRequest());
}
YAML
# Automated dependency-debt repayment with OpenRewrite (Spring Boot upgrade recipe)
type: specs.openrewrite.org/v1beta/recipe
name: com.shopnow.UpgradeBoot
recipeList:
  - org.openrewrite.java.spring.boot4.UpgradeSpringBoot_4_0
  - org.openrewrite.java.migrate.UpgradeToJava25

6Practical Examples

6.1 Pricing duplication

Pricing was computed in cart, checkout, order and invoice services with subtle differences. Interest: 2 customer-visible price mismatches per quarter and every promotion change touching 4 codebases. Principal: 8 days to route all to PricingService. Payback period: about 2 months.

6.2 Framework end-of-life

Thirty-four services on Spring Boot 2.7 (EOL) accumulated CVE exposure. OpenRewrite recipes did ~80% of the upgrade automatically; remaining work was scheduled by service criticality. Dependency debt became a standing platform KPI.

6.3 Dormant ugly code

A legacy report module had poor structure but changed twice in two years. Interest was near zero; it was left alone and fenced with tests. Effort went to hotspots instead.

6.4 Worked example — hotspot table

FileCommits (6 m)ComplexityHotspot rank
OrderService.java142high1
PromotionEngine.java98high2
TaxCalculator.java41medium5
LegacyReport.java2very high— (dormant)
Table 7 — Change frequency decides priority

6.5 Worked example — cost of delay vs cost of fix

ItemPrincipalInterest/quarterPayback
Pricing duplication8 d4 d2 quarters
OrderService god class30 d6 d5 quarters
Missing refund tests5 drisk of Sev-1immediate
Table 8 — Compare like a loan

6.6 Worked example — debt categories in the register

KindOpen itemsOldest
Design142 years
Dependency96 months
Test111 year
Infrastructure518 months
Documentation71 year
Table 9 — Visibility by kind

6.7 Worked example — interview whiteboard

PromptAnswer outline
"How do you manage technical debt?""Treat it as a loan: principal to fix, interest paid on each change. Classify by intent (Fowler's quadrant) and kind. Find interest with evidence — hotspots from churn × complexity, defect density, lead time, change coupling — rather than code aesthetics. Keep a register with owners, interest and triggers. Take deliberate debt only with an ADR and repayment trigger. Repay continuously (boy-scout rule, ~20% capacity) and in targeted projects for hotspots; automate dependency upgrades."
Table 10 — Structured answer

6.8 Worked example — making debt visible to product

FramingMessage
Lead time"Promotions features take 2× longer because of DEBT-042"
Risk"Refund flow has no tests — a regression could cost ₹X"
Opportunity"Fixing it saves ~4 engineer-days per quarter"
Table 11 — Speak in delivery and risk, not code

6.9 Worked example — boy-scout rule in practice

When touching a fileDo
Unclear namesRename
Long method you changedExtract helpers
Missing test for your changeAdd it
Big refactor neededRegister it, don't sneak it in
Table 12 — Small continuous repayment

6.10 Worked example — debt from AI-generated code

RiskControl
Plausible but duplicated logicReview against existing modules
Inconsistent patternsTemplates, ArchUnit rules
Untested generated codeTests required in PR
Table 13 — New source of inadvertent debt

6.11 Worked example — SonarQube debt ratio

ServiceDebt ratioHotspot?Action
checkout3%yesmaintain
orders9%yesrefactor OrderService
legacy-admin22%nofence + replace later
Table 14 — Tool estimates plus hotspot context

6.12 Worked example — change coupling reveals design debt

Files that change together (80% of commits)Meaning
OrderService + InvoiceService + TaxCalculatorPricing logic spread; consolidate
CheckoutController + PromotionEngine + CartMapperPromotion rules leak into controller
Table 15 — Git reveals hidden coupling

6.13 Worked example — repayment strategies

StrategyWhen
Continuous (boy scout)Small debt in active code
Planned slice (20%)Medium items from register
Dedicated projectLarge design debt in hotspots
Strangler replacementLegacy modules (Chapter 6, case 7.1)
AcceptDormant code with low interest
Table 16 — Match strategy to debt

6.14 Worked example — debt KPIs

KPITrend
Services on supported framework versions62% → 97%
Hotspot complexity (top 10 files)−35%
Register items older than 1 year18 → 6
Lead time in hotspot modules−40%
Table 17 — Debt management with outcomes

6.15 Worked example — debt review cadence

ForumFrequencyOutput
Team retroEvery sprintNew register items
Architecture reviewMonthlyCross-team design debt decisions
Engineering leadershipQuarterlyDebt KPIs, funding for large items
Table 18 — Debt managed as a portfolio

6.16 Worked example — debt interest from incidents

IncidentDebt linkedCost
INC-5011 wrong tax on invoicesDEBT-042 (duplicated pricing)₹4.2 L refunds + 3 eng-days
INC-5230 refund stuckDEBT-051 (no refund tests)Sev-2, 6 h
Table 19 — Post-mortems quantify interest

6.17 Worked example — PayGrid regulated debt

DebtWhy it can't waitPlan
TLS library on old versionPCI requirement for supported cryptoUpgrade within 30 days
Manual key-rotation scriptAudit findingAutomate via KMS rotation + evidence
Settlement module with no testsMoney correctness riskCharacterisation tests before any change
Table 20 — Compliance turns some debt into obligations

6.18 Worked example — debt vs feature decision

OptionDelivery time for next 3 promotion features
Build on duplicated pricing3 × 9 days = 27 days
Repay DEBT-042 first (8 d) then build8 + 3 × 5 days = 23 days
Table 21 — Repayment first is faster by the third feature

6.19 Worked example — test debt

SymptomDebtRepayment
Engineers afraid to touch refundsNo tests on refund state transitionsCharacterisation tests from production event logs
Flaky suite ignoredNon-deterministic tests (3.1)Quarantine + fix within 1 week
Slow suite skipped locallyAll tests use full Spring contextPorts and unit tests (3.2)
Table 22 — Test debt raises the interest on all other debt

6.20 Worked example — infrastructure debt

DebtInterestRepayment
Hand-built EC2 for report jobsEvery change manual, drift, no DRMove to Terraform + containers
Snowflake Jenkins jobsUnknown build stepsPipelines as code (GitHub Actions)
Manually managed DNSOutage risk on changesRoute 53 via IaC
Table 23 — Debt lives outside application code too

7Common Problems & Mistakes

MistakeFix
Refactoring ugly but dormant codePrioritise by interest (hotspots)
Invisible debtRegister with owners
Reckless debt called "MVP"ADR + trigger
Asking for "a refactoring sprint" without dataShow lead time/defect impact
Big-bang rewritesIncremental repayment, strangler
Ignoring dependency debtAutomated upgrades, KPIs
Table 24 — Mistakes and fixes

8Performance & Production Considerations

ConsiderationPractice
Risk of refactoringTests first; small steps; feature flags
Upgrade windowsAutomated recipes; canary deploys
Tracking effortTag PRs with debt id
Table 25 — Operating debt repayment

Paying down debt changes working production code, so the main concern is doing it without causing incidents. Put characterisation tests around an area before changing it, especially in money-handling code such as refunds and settlement, and refactor in small, reversible steps behind feature flags or a strangler interface rather than in one long-lived branch. For dependency and framework upgrades, apply automated recipes, then roll out with canary deployments and compare error rates and latency against the previous version before promoting. Watch performance as well as behaviour, because consolidating logic or changing a framework version can alter allocation, query patterns or startup time. Finally, tag pull requests with the debt-register id, so repayment effort is measured, the reduction in interest can be checked against lead-time and defect data, and the register stays accurate.

9Architecture & Design Considerations

GuidelineDetail
Architecture limits debt spreadBoundaries contain debt
Fitness functions stop new debtArchUnit, dependency checks
Plan capacity~20% for debt/maintenance
Link to Chapter 5Architectural debt and prioritisation
Table 26 — Design guidance

10Interview Questions & Answers

Q1BasicConcept

What is technical debt?

Technical debt is the implied future cost of choosing an expedient solution now instead of a better one. Ward Cunningham's metaphor compares it to a loan: the shortcut buys speed today, and you pay it back later with interest. The principal is the effort needed to fix the shortcut; the interest is the extra effort, defects and risk that every change in that area pays while the debt remains. It covers more than messy code: design, tests, dependencies, infrastructure and documentation can all carry it. Taken deliberately and repaid, debt is a legitimate tool; left unmanaged, it compounds until change becomes prohibitively expensive.

Q2BasicConcept

Principal vs interest?

Principal is the one-off cost of removing the debt; interest is the recurring cost you pay for as long as it stays. At ShopNow, pricing logic duplicated across cart, checkout, order and invoice had a principal of about eight days, the effort to route everything through one PricingService. The interest was every promotion change touching four codebases, about half a day extra each time, plus two customer-visible price mismatches a quarter. The interest rate depends on how often the area changes multiplied by the pain per change. That is why the same ugly code can be expensive in a hotspot and almost free in a dormant module.

Q3IntermediateConcept

Explain Fowler's quadrant.

Martin Fowler's quadrant classifies debt on two axes: deliberate or inadvertent, and reckless or prudent. Deliberate and prudent debt is a conscious shortcut with a plan, such as hard-coding sale pricing for two weeks with an ADR and a repayment trigger; record it and repay it. Deliberate and reckless debt means knowingly cutting corners without a plan, which should be challenged in design review. Inadvertent and prudent debt is normal learning: you discover a better design afterwards and refactor as understanding improves. Inadvertent and reckless debt comes from gaps in skill and needs training, reviews and templates. Each quadrant needs a different response.

Q4IntermediateTricky

Is all technical debt bad?

No. Deliberate, prudent debt can be a good business decision when speed matters more than perfection, for example meeting a Diwali sale deadline or validating a product before investing in its design. The conditions are that it is decided explicitly, recorded in an ADR and the debt register, contained behind a flag or interface, and given a repayment trigger such as the sprint after the sale. Debt in code that rarely changes also costs almost nothing and can be left alone. What is bad is invisible, unowned debt, and reckless debt labelled as an MVP with no plan to repay it, because its interest compounds silently.

Q5Senior EngineerScenario

How do you find the most expensive debt?

Follow the interest, using evidence rather than code aesthetics. Run a hotspot analysis: code that is both complex and frequently changed, measured by Git churn multiplied by complexity, is where debt charges the most interest. Add change-coupling analysis to find files that always change together, which reveals scattered design debt. Combine this with defect density per module from the issue tracker, lead time per module from pull-request data, incident post-mortems, and developer pain from retros and on-call notes. At ShopNow, OrderService had 142 commits in six months and high complexity, so it ranked first, while a very complex but dormant report module did not rank at all.

Q6Senior EngineerDesign

What goes in a debt register?

Each entry needs enough information to support a funding decision. Include an id and title, the kind of debt, such as design, test, dependency or infrastructure, and an owning team. Record interest with evidence, such as change frequency, defects and extra effort per change, and the principal, the estimated effort to fix it. Add a repayment trigger, for example before the next promotions feature, and a status. Link the ADR if the debt was deliberate, and any incidents it caused. ShopNow keeps the register as YAML files in each repository, so entries are reviewed with code, referenced from pull requests and reported to leadership every quarter.

Q7Technical ArchitectBehavioural

How do you get time to pay debt?

Speak in delivery and risk, not code quality. Product managers will not fund a refactoring sprint, but they will fund a faster roadmap. Show that promotion features take twice as long because of duplicated pricing, that repaying it first delivers the next three features in 23 days instead of 27, or that untested refunds risk a Sev-1 incident costing a stated sum. Back this with lead-time, defect and incident data. Then use three mechanisms: a standing share of capacity, around 20%, for continuous repayment; repayment bundled into the features that touch the debt; and dedicated projects for large hotspots, approved like any other investment.

Q8Technical ArchitectScenario

How do you handle dependency debt at scale?

Automate it and track it as a platform KPI. Use OpenRewrite recipes to perform most of a framework or JDK upgrade automatically, for example Spring Boot and Java 25 migrations, and Renovate or Dependabot for routine version bumps. Centralise versions with a shared BOM or Gradle platform so services move together. Report the percentage of services on supported versions to engineering leadership, and schedule the remaining manual work by service criticality. When 34 ShopNow services sat on end-of-life Spring Boot 2.7, recipes did about 80% of the work. Good tests and canary deploys make frequent upgrades safe, so dependency debt never accumulates into a big-bang migration.

Q9Solution ArchitectTrade-off

Should you refactor dormant legacy code?

Usually not. Debt only charges interest when code changes, so a legacy module that changes twice in two years costs almost nothing however ugly it is, and refactoring it carries risk for little return. ShopNow left such a report module alone, fenced it with characterisation tests and spent the effort on hotspots instead. The exceptions matter: if the code is about to become active because of a planned feature, if it carries security or compliance risk such as unsupported crypto libraries, or if it blocks a platform migration, repay it or replace it with a strangler. The decision follows interest and risk, not appearance.

Q10Solution ArchitectDesign

How does architecture relate to debt?

Architecture decides how far debt spreads and how expensive it becomes. Good boundaries, with modules or services behind clear interfaces, contain debt, so a messy implementation inside one module does not leak into every change elsewhere and can later be replaced with a strangler. Poor boundaries turn local shortcuts into structural debt that touches many teams, visible as change coupling between components. Architects also prevent new debt with fitness functions: ArchUnit rules, dependency checks and module boundaries enforced in the build. Finally, architectural debt, such as a wrong service decomposition, is the most expensive kind, so it is tracked and prioritised explicitly.

Newsletter

Get new tutorials every week

New ArchitectMind tutorials and interview questions, once a week. No spam, unsubscribe anytime.

Almost done! Check your inbox and click the confirmation link to complete your subscription.
Keep going in Book 8

Architecture Quality Attributes

Volume I · Topic 1.1

Quality Attributes

Quality Attributes & Architectural Thinking

Volume I · Topic 1.2

Why Quality Attributes Matter

Quality Attributes & Architectural Thinking

Volume I · Topic 1.3

Quality Attribute Categories

Quality Attributes & Architectural Thinking

Volume II · Topic 1.1

Performance

Performance & Capacity

Volume II · Topic 1.2

Latency Components

Performance & Capacity

Volume II · Topic 1.3

Tail Latency

Performance & Capacity

Volume III · Topic 1.1

Scalability

Scalability & Elasticity

Volume III · Topic 1.2

Vertical Scaling

Scalability & Elasticity

Volume III · Topic 1.3

Horizontal Scaling

Scalability & Elasticity

Volume IV · Topic 1.1

Availability Fundamentals

Availability, Reliability & Resilience

Volume IV · Topic 1.2

Availability Calculation

Availability, Reliability & Resilience

Volume IV · Topic 1.3

Nines of Availability

Availability, Reliability & Resilience

Volume V · Topic 1.1

Security Requirements

Security, Privacy & Compliance

Volume V · Topic 1.2

Security Architecture

Security, Privacy & Compliance

Volume V · Topic 1.3

Threat Modeling

Security, Privacy & Compliance

Volume VI · Topic 1.1

Maintainability

Maintainability, Modifiability & Testability

Volume VI · Topic 1.3

Code Quality

Maintainability, Modifiability & Testability

Volume VII · Topic 1.1

What Is Operability?

Operability, Observability & Deployability

Volume VII · Topic 1.2

Operational Readiness

Operability, Observability & Deployability

Volume VII · Topic 1.3

Production Readiness Checklist

Operability, Observability & Deployability

Volume VIII · Topic 1.1

Why Evaluate Architecture?

Quality Attribute Trade-offs & Architecture Evaluation

Volume VIII · Topic 1.2

Architecture Evaluation Methods

Quality Attribute Trade-offs & Architecture Evaluation

Volume VIII · Topic 1.3

ATAM

Quality Attribute Trade-offs & Architecture Evaluation