Technical debt is the cost of additional work caused by choosing an easy solution now instead of a better approach that would take longer. Like financial debt, it accumulates interest—the longer it remains, the more it costs.
Understanding Technical Debt#
Types of Technical Debt#
Intentional Debt:
- "Ship now, refactor later"
- Known shortcuts for deadlines
- Documented trade-offs
Unintentional Debt:
- Outdated patterns
- Evolved requirements
- Team knowledge gaps
- Poor initial design
Bit Rot:
- Dependencies falling behind
- Deprecated APIs
- Security vulnerabilities
The Debt Quadrant#
Reckless Prudent
┌─────────────────────┬─────────────────────┐
│ "We don't have time │ "Ship now, refactor │
Deliberate │ for design" │ later" │
│ │ │
│ High risk, often │ Acceptable if │
│ regretted │ tracked │
├─────────────────────┼─────────────────────┤
│ "What's layering?" │ "Now we know how │
Inadvertent│ │ we should have │
│ Knowledge gap, │ done it" │
│ needs training │ │
│ │ Natural learning │
└─────────────────────┴─────────────────────┘
Identifying Technical Debt#
Code Symptoms#
1// Debt indicators in code:
2
3// 1. Long functions
4function processOrder(order: Order): ProcessedOrder {
5 // 500 lines of intertwined logic
6}
7
8// 2. Deep nesting
9if (user) {
10 if (user.isActive) {
11 if (user.hasPermission) {
12 if (order.isValid) {
13 // actual logic buried here
14 }
15 }
16 }
17}
18
19// 3. Duplicated code
20// Same logic in multiple files
21
22// 4. Magic numbers/strings
23const timeout = 86400000; // What is this?
24if (status === 'A') { // What's A?
25
26// 5. Comments explaining confusing code
27// This calculates the adjusted price considering the
28// legacy discount system, old tax rules, and...
29
30// 6. Disabled tests
31// eslint-disable-next-line
32// @ts-ignore
33// TODO: fix this testMetrics to Track#
1// Track these metrics over time
2interface DebtMetrics {
3 // Code quality
4 testCoverage: number; // Target: >80%
5 duplicateCodePercentage: number; // Target: <3%
6 cyclomaticComplexity: number; // Target: <10 per function
7
8 // Velocity impact
9 bugFixTime: number; // Hours to fix average bug
10 featureTime: number; // Days to ship average feature
11 deploymentFrequency: number; // Deploys per week
12
13 // Dependencies
14 outdatedDependencies: number; // Packages behind latest
15 securityVulnerabilities: number; // Known CVEs
16}Measuring Technical Debt#
The Interest Rate Model#
1// Calculate the "interest" debt charges
2
3interface DebtItem {
4 description: string;
5 location: string;
6
7 // Time tax per interaction
8 minutesAddedPerChange: number;
9
10 // How often this area changes
11 changesPerMonth: number;
12
13 // Risk of bugs when changing
14 bugRisk: 'low' | 'medium' | 'high';
15}
16
17function calculateMonthlyInterest(debt: DebtItem): number {
18 const riskMultiplier = {
19 low: 1,
20 medium: 1.5,
21 high: 2.5,
22 };
23
24 return (
25 debt.minutesAddedPerChange *
26 debt.changesPerMonth *
27 riskMultiplier[debt.bugRisk]
28 );
29}
30
31// Example
32const legacyPaymentSystem: DebtItem = {
33 description: 'Legacy payment integration',
34 location: 'src/payments/',
35 minutesAddedPerChange: 60,
36 changesPerMonth: 4,
37 bugRisk: 'high',
38};
39
40// Monthly interest: 60 * 4 * 2.5 = 600 minutes = 10 hours/monthPrioritization Matrix#
High Interest Low Interest
┌──────────────────┬──────────────────┐
│ │ │
High Cost │ Schedule soon │ Plan for later │
to Fix │ (high impact │ (worth doing │
│ but expensive) │ but not urgent)│
├──────────────────┼──────────────────┤
│ │ │
Low Cost │ Fix now │ Boy Scout Rule │
to Fix │ (quick wins) │ (fix when │
│ │ touched) │
└──────────────────┴──────────────────┘
Paying Down Debt#
The 20% Rule#
Allocate 20% of engineering time to debt reduction:
- 1 day per week
- 2 days per sprint
- 1 sprint per quarter
Make it a budget, not a stretch goal.
Debt Sprints#
Quarterly "tech debt sprint":
- 2 weeks focused on debt
- Measurable goals
- No new features
Benefits:
- Deep focus on improvements
- Team morale boost
- Significant progress
Incremental Refactoring#
1// Boy Scout Rule: Leave code cleaner than you found it
2
3// When touching a file for a feature:
4// 1. Make the feature change
5// 2. Improve one small thing
6
7// Before (while adding new payment method)
8function processPayment(method: string, amount: number) {
9 if (method === 'cc') {
10 // credit card logic
11 } else if (method === 'pp') {
12 // paypal logic
13 } else if (method === 'stripe') {
14 // stripe logic
15 }
16 // Adding: apple pay
17}
18
19// After (feature + improvement)
20interface PaymentProcessor {
21 process(amount: number): Promise<PaymentResult>;
22}
23
24const processors: Record<string, PaymentProcessor> = {
25 credit_card: new CreditCardProcessor(),
26 paypal: new PayPalProcessor(),
27 stripe: new StripeProcessor(),
28 apple_pay: new ApplePayProcessor(), // New feature
29};
30
31async function processPayment(
32 method: string,
33 amount: number,
34): Promise<PaymentResult> {
35 const processor = processors[method];
36 if (!processor) throw new Error(`Unknown payment method: ${method}`);
37 return processor.process(amount);
38}Strangler Fig Pattern#
1// Gradually replace legacy system
2
3// Phase 1: Route through new code
4class PaymentRouter {
5 async process(payment: Payment): Promise<Result> {
6 if (this.shouldUseLegacy(payment)) {
7 return this.legacySystem.process(payment);
8 }
9 return this.newSystem.process(payment);
10 }
11
12 private shouldUseLegacy(payment: Payment): boolean {
13 // Start: 100% legacy
14 // Gradually increase new system usage
15 return !featureFlags.isEnabled('new-payment-system', {
16 userId: payment.userId,
17 percentage: 10, // Start with 10%
18 });
19 }
20}
21
22// Phase 2: Increase new system percentage
23// Phase 3: Remove legacy codePreventing New Debt#
Definition of Done#
1## Definition of Done Checklist
2
3- [ ] Tests written and passing (>80% coverage for new code)
4- [ ] No new linting errors
5- [ ] Code reviewed by 2+ engineers
6- [ ] Documentation updated
7- [ ] No known TODOs left uncommented
8- [ ] Performance impact assessed
9- [ ] No new dependencies without approvalArchitectural Decision Records#
1# ADR-001: Use PostgreSQL for Primary Database
2
3## Status
4Accepted
5
6## Context
7We need to choose a database for the new user service.
8
9## Decision
10Use PostgreSQL with Prisma ORM.
11
12## Consequences
13- Pros: Strong consistency, JSON support, team familiarity
14- Cons: Vertical scaling limits, managed service costs
15- Trade-offs accepted: We accept scaling limitations for
16 consistency guarantees
17
18## Debt implications
19- Will need sharding strategy if we exceed 100M users
20- Must monitor query performance from day 1Code Review Gates#
1// Automated checks in CI/CD
2
3// .github/workflows/quality.yml
4name: Code Quality
5
6on: [pull_request]
7
8jobs:
9 quality:
10 runs-on: ubuntu-latest
11 steps:
12 - name: Test Coverage
13 run: |
14 coverage=$(npm test -- --coverage | grep 'All files')
15 if [[ $coverage < 80 ]]; then
16 echo "Coverage below 80%"
17 exit 1
18 fi
19
20 - name: Complexity Check
21 run: npx eslint --rule 'complexity: [error, 10]'
22
23 - name: Duplicate Code
24 run: npx jscpd --threshold 3
25
26 - name: Dependency Audit
27 run: npm audit --audit-level highCommunication#
Debt Register#
1# Technical Debt Register
2
3## Active Debt Items
4
5### HIGH PRIORITY
6
7#### DEBT-001: Legacy Authentication System
8- **Location**: src/auth/
9- **Interest**: ~15 hours/month
10- **Fix Cost**: 2 weeks
11- **Owner**: @security-team
12- **Status**: In Progress (Sprint 24)
13
14### MEDIUM PRIORITY
15
16#### DEBT-002: Monolithic Order Service
17- **Location**: src/orders/
18- **Interest**: ~8 hours/month
19- **Fix Cost**: 1 month
20- **Owner**: Unassigned
21- **Status**: Planned Q3
22
23### LOW PRIORITY
24
25#### DEBT-003: Outdated Test Framework
26- **Location**: test/
27- **Interest**: ~2 hours/month
28- **Fix Cost**: 3 days
29- **Owner**: @platform-team
30- **Status**: BacklogReporting to Stakeholders#
Monthly Tech Debt Report
Debt Score: 72/100 (improved from 68)
Top 3 Debt Items:
1. Legacy payment system - actively being replaced
2. Outdated user service - planned for Q3
3. Test flakiness - assigned to platform team
Impact This Month:
- 2 incidents caused by legacy code
- ~40 hours of extra debugging time
- 3 features delayed due to complexity
Progress:
- Completed: Migration of auth system
- In Progress: Payment system rewrite (60% done)
- Planned: User service modernization
Requested: 2 additional sprints for Q3 debt work
Conclusion#
Technical debt is inevitable—the goal is management, not elimination. Track it, measure its impact, and pay it down systematically. Make debt reduction part of regular work, not a separate initiative.
Remember: the best time to address technical debt was when it was created. The second best time is now.