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
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.
Figure 1 — Principal and interest
Term
Meaning
Example
Principal
Effort to remove the debt
8 days to consolidate pricing logic
Interest
Recurring cost while debt exists
Every pricing change touches 4 places; 2 bugs/quarter
Interest rate
How often the debt area changes × pain per change
High for hot code, near zero for dormant code
Default
Debt so large a rewrite seems the only option
"Legacy admin must be rebuilt"
Table 1 — Vocabulary
Debt vocabulary
Meaning
Hotspot
Code that is both complex and frequently changed
Change coupling
Files that repeatedly change together
Boy-scout rule
Leave code better than you found it
Debt register
Tracked list of known debt with owner and interest
Strangler
Replace a legacy part incrementally behind an interface
Table 2 — Terms
2Why It Exists
Cause
Example
Deadline pressure
"Hard-code the tax table for the sale; parameterise later"
Learning
The domain model was wrong because the domain wasn't understood yet
Entropy
Many small changes without refactoring
Ageing dependencies
Framework versions reach end of life
Skill gaps
Patterns misapplied
Table 3 — Where debt comes from
Figure 2 — Hidden vs managed debt
3Core Concepts — Technical Debt
Figure 3 — Technical debt quadrant (Fowler)
Quadrant
Response
Deliberate & prudent
Record it, set a repayment trigger
Deliberate & reckless
Challenge in design review
Inadvertent & prudent
Normal learning — refactor as understanding improves
Inadvertent & reckless
Skills, reviews, templates
Table 4 — How to respond per quadrant
Figure 4 — Kinds of technical debt
4How It Works Internally — Finding the Interest
Signal
Source
Hotspots: churn × complexity
Git history + static analysis (CodeScene-style)
Defect density per module
Issue tracker linked to code
Lead time per module
PR data
Change coupling (files that always change together)
Git log analysis
Developer pain
Surveys, 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
// Paying it down: one PricingService used everywhere (duplicates delegated, then deleted)@Deprecated(forRemoval = true) // DEBT-042: remove after all callers migratepublic Money legacyCartPrice(Cart cart) {return pricing.priceOf(cart.toPricingRequest());}
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
File
Commits (6 m)
Complexity
Hotspot rank
OrderService.java
142
high
1
PromotionEngine.java
98
high
2
TaxCalculator.java
41
medium
5
LegacyReport.java
2
very high
— (dormant)
Table 7 — Change frequency decides priority
6.5 Worked example — cost of delay vs cost of fix
Item
Principal
Interest/quarter
Payback
Pricing duplication
8 d
4 d
2 quarters
OrderService god class
30 d
6 d
5 quarters
Missing refund tests
5 d
risk of Sev-1
immediate
Table 8 — Compare like a loan
6.6 Worked example — debt categories in the register
Kind
Open items
Oldest
Design
14
2 years
Dependency
9
6 months
Test
11
1 year
Infrastructure
5
18 months
Documentation
7
1 year
Table 9 — Visibility by kind
6.7 Worked example — interview whiteboard
Prompt
Answer 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
Framing
Message
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 file
Do
Unclear names
Rename
Long method you changed
Extract helpers
Missing test for your change
Add it
Big refactor needed
Register it, don't sneak it in
Table 12 — Small continuous repayment
6.10 Worked example — debt from AI-generated code
Risk
Control
Plausible but duplicated logic
Review against existing modules
Inconsistent patterns
Templates, ArchUnit rules
Untested generated code
Tests required in PR
Table 13 — New source of inadvertent debt
6.11 Worked example — SonarQube debt ratio
Service
Debt ratio
Hotspot?
Action
checkout
3%
yes
maintain
orders
9%
yes
refactor OrderService
legacy-admin
22%
no
fence + 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 + TaxCalculator
Pricing logic spread; consolidate
CheckoutController + PromotionEngine + CartMapper
Promotion rules leak into controller
Table 15 — Git reveals hidden coupling
6.13 Worked example — repayment strategies
Strategy
When
Continuous (boy scout)
Small debt in active code
Planned slice (20%)
Medium items from register
Dedicated project
Large design debt in hotspots
Strangler replacement
Legacy modules (Chapter 6, case 7.1)
Accept
Dormant code with low interest
Table 16 — Match strategy to debt
6.14 Worked example — debt KPIs
KPI
Trend
Services on supported framework versions
62% → 97%
Hotspot complexity (top 10 files)
−35%
Register items older than 1 year
18 → 6
Lead time in hotspot modules
−40%
Table 17 — Debt management with outcomes
6.15 Worked example — debt review cadence
Forum
Frequency
Output
Team retro
Every sprint
New register items
Architecture review
Monthly
Cross-team design debt decisions
Engineering leadership
Quarterly
Debt KPIs, funding for large items
Table 18 — Debt managed as a portfolio
6.16 Worked example — debt interest from incidents
Incident
Debt linked
Cost
INC-5011 wrong tax on invoices
DEBT-042 (duplicated pricing)
₹4.2 L refunds + 3 eng-days
INC-5230 refund stuck
DEBT-051 (no refund tests)
Sev-2, 6 h
Table 19 — Post-mortems quantify interest
6.17 Worked example — PayGrid regulated debt
Debt
Why it can't wait
Plan
TLS library on old version
PCI requirement for supported crypto
Upgrade within 30 days
Manual key-rotation script
Audit finding
Automate via KMS rotation + evidence
Settlement module with no tests
Money correctness risk
Characterisation tests before any change
Table 20 — Compliance turns some debt into obligations
6.18 Worked example — debt vs feature decision
Option
Delivery time for next 3 promotion features
Build on duplicated pricing
3 × 9 days = 27 days
Repay DEBT-042 first (8 d) then build
8 + 3 × 5 days = 23 days
Table 21 — Repayment first is faster by the third feature
6.19 Worked example — test debt
Symptom
Debt
Repayment
Engineers afraid to touch refunds
No tests on refund state transitions
Characterisation tests from production event logs
Flaky suite ignored
Non-deterministic tests (3.1)
Quarantine + fix within 1 week
Slow suite skipped locally
All tests use full Spring context
Ports and unit tests (3.2)
Table 22 — Test debt raises the interest on all other debt
6.20 Worked example — infrastructure debt
Debt
Interest
Repayment
Hand-built EC2 for report jobs
Every change manual, drift, no DR
Move to Terraform + containers
Snowflake Jenkins jobs
Unknown build steps
Pipelines as code (GitHub Actions)
Manually managed DNS
Outage risk on changes
Route 53 via IaC
Table 23 — Debt lives outside application code too
7Common Problems & Mistakes
Mistake
Fix
Refactoring ugly but dormant code
Prioritise by interest (hotspots)
Invisible debt
Register with owners
Reckless debt called "MVP"
ADR + trigger
Asking for "a refactoring sprint" without data
Show lead time/defect impact
Big-bang rewrites
Incremental repayment, strangler
Ignoring dependency debt
Automated upgrades, KPIs
Table 24 — Mistakes and fixes
8Performance & Production Considerations
Consideration
Practice
Risk of refactoring
Tests first; small steps; feature flags
Upgrade windows
Automated recipes; canary deploys
Tracking effort
Tag 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
Guideline
Detail
Architecture limits debt spread
Boundaries contain debt
Fitness functions stop new debt
ArchUnit, dependency checks
Plan capacity
~20% for debt/maintenance
Link to Chapter 5
Architectural 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.
Your subscription could not be saved. Please try again.
Almost done! Check your inbox and click the confirmation link to complete your subscription.