Skip to content
SP StackPractices
intermediate By Mathias Paulenko

Technical Debt: Track, Prioritize, Pay Down

Master technical debt management: identify debt types, track with debt registers, prioritize using impact vs effort, schedule paydown sprints, and measure debt reduction.

Topics: testing

Note: This guide follows English-language naming conventions and terminology standards common in international development teams. Examples use English identifiers and comments to maximize compatibility across codebases and tooling.

Introduction

Technical debt is the cost of shortcuts taken during development. Like financial debt, it accrues interest: the longer you wait to pay it down, the more expensive it becomes. The following walks through identifying debt types, tracking with a debt register, prioritizing using impact vs effort, scheduling paydown sprints, and measuring debt reduction over time.

Types of Technical Debt

┌─────────────────────────────────────────────────────┐
│              Technical Debt Types                    │
├──────────────────┬──────────────────────────────────┤
│ Deliberate       │ Conscious shortcut to meet a     │
│                  │ deadline. Documented.            │
├──────────────────┼──────────────────────────────────┤
│ Accidental       │ Poor design decisions made in    │
│                  │ ignorance. Discovered later.     │
├──────────────────┼──────────────────────────────────┤
│ Bit Rot          │ Code that was fine when written  │
│                  │ but the environment changed.     │
├──────────────────┼──────────────────────────────────┤
│ Dependency       │ Outdated libraries, frameworks,  │
│                  │ or language versions.            │
├──────────────────┼──────────────────────────────────┤
│ Test Debt        │ Missing or inadequate test       │
│                  │ coverage for critical paths.     │
├──────────────────┼──────────────────────────────────┤
│ Documentation    │ Missing or outdated docs,        │
│                  │ architecture diagrams, runbooks. │
├──────────────────┼──────────────────────────────────┤
│ Infrastructure   │ Manual deployments, no CI/CD,    │
│                  │ missing monitoring.              │
└──────────────────┴──────────────────────────────────┘

Debt Register

Track all technical debt in a structured register. This makes debt visible and measurable.

# Technical Debt Register

## TD-001: Monolithic auth module
- **Type**: Deliberate
- **Date Added**: 2026-01-15
- **Impact**: High — blocks multi-tenant support
- **Effort**: 5 days
- **Interest**: Every new feature touches auth, adding 2-3 days per feature
- **Status**: Open
- **Owner**: Backend team
- **Resolution**: Split into per-tenant auth service

## TD-002: No integration tests for payment flow
- **Type**: Test debt
- **Date Added**: 2026-02-01
- **Impact**: Critical — payment bugs reach production
- **Effort**: 3 days
- **Interest**: Each manual regression test takes 4 hours
- **Status**: Open
- **Owner**: QA team
- **Resolution**: Add Playwright integration tests for payment flow

## TD-003: jQuery in legacy admin panel
- **Type**: Bit rot
- **Date Added**: 2026-03-10
- **Impact**: Low — admin panel works, few users
- **Effort**: 10 days
- **Interest**: New developers struggle with jQuery patterns
- **Status**: Open
- **Owner**: Frontend team
- **Resolution**: Migrate to React in Q3

Prioritization Matrix

         High Impact

    Pay Now │ Pay Next
    (Q1)    │ (Q2)
  ──────────┼──────────
    Monitor │ Ignore
    (Q3)    │ (N/A)

         Low Impact
    Low Effort    High Effort
// utils/debtPrioritizer.ts — Prioritize debt items
interface DebtItem {
  id: string;
  title: string;
  impact: 'critical' | 'high' | 'medium' | 'low';
  effort: number; // in days
  interestRate: number; // days added per feature
  ageInDays: number;
}

function prioritizeDebt(items: DebtItem[]): DebtItem[] {
  return items
    .map(item => ({
      ...item,
      // Score: impact weight + accumulated interest - effort cost
      score: impactWeight(item.impact) +
             (item.interestRate * item.ageInDays / 30) -
             (item.effort * 0.5),
    }))
    .sort((a, b) => b.score - a.score);
}

function impactWeight(impact: string): number {
  const weights = { critical: 100, high: 60, medium: 30, low: 10 };
  return weights[impact] ?? 0;
}

// Usage
const prioritized = prioritizeDebt([
  { id: 'TD-001', title: 'Monolithic auth', impact: 'high', effort: 5, interestRate: 2.5, ageInDays: 180 },
  { id: 'TD-002', title: 'No payment tests', impact: 'critical', effort: 3, interestRate: 4, ageInDays: 120 },
  { id: 'TD-003', title: 'jQuery admin', impact: 'low', effort: 10, interestRate: 0.5, ageInDays: 90 },
]);

// Result: TD-002 (critical, high interest) > TD-001 (high impact) > TD-003 (low priority)

Paydown Strategies

Fixed Percentage Per Sprint

Dedicate a fixed percentage of each sprint to debt paydown. 15-20% is common.

// Sprint planning with debt allocation
const SPRINT_CAPACITY = 80; // story points
const DEBT_ALLOCATION = 0.20; // 20%

const debtPoints = Math.floor(SPRINT_CAPACITY * DEBT_ALLOCATION); // 16 points
const featurePoints = SPRINT_CAPACITY - debtPoints; // 64 points

// Select debt items that fit in debtPoints
const sprintDebtItems = prioritizedDebt
  .filter(item => item.effort * 3 <= debtPoints) // rough points = effort * 3
  .slice(0, 2); // top 2 items

Debt Sprint

Dedicate an entire sprint to debt paydown every quarter.

Sprint 1: Features
Sprint 2: Features
Sprint 3: Features
Sprint 4: Debt Paydown (all capacity)
Sprint 5: Features
...

Boy Scout Rule

“Always leave the code better than you found it.” Fix small debt items whenever you touch the surrounding code.

// When fixing a bug in the payment module:
// 1. Fix the bug
// 2. Extract the long function into smaller ones
// 3. Add a missing test for the edge case
// 4. Rename an unclear variable
// 5. Update the outdated comment
//
// Each change is small, but over time the module improves

Stop the Bleeding

Before paying down old debt, stop accumulating new debt.

1. Add linting rules to prevent known anti-patterns
2. Add pre-commit hooks for code quality checks
3. Require code review for all PRs
4. Add CI checks for test coverage thresholds
5. Document architecture decisions (ADRs)
6. Pair program on complex changes

Measuring Debt

Code metrics

// scripts/measureDebt.ts — Measure technical debt metrics
import { execSync } from 'child_process';

interface DebtMetrics {
  cyclomaticComplexity: number;
  duplicationPercentage: number;
  testCoverage: number;
  todoCount: number;
  outdatedDependencies: number;
  averageFunctionLength: number;
  maxNestingDepth: number;
}

function measureDebt(): DebtMetrics {
  return {
    cyclomaticComplexity: measureComplexity(),
    duplicationPercentage: measureDuplication(),
    testCoverage: measureCoverage(),
    todoCount: countTodos(),
    outdatedDependencies: countOutdatedDeps(),
    averageFunctionLength: measureAvgFunctionLength(),
    maxNestingDepth: measureMaxNesting(),
  };
}

function countTodos(): number {
  const output = execSync('grep -rn "TODO\\|FIXME\\|HACK\\|XXX" src/ --include="*.ts" | wc -l', { encoding: 'utf-8' });
  return parseInt(output.trim(), 10);
}

function countOutdatedDeps(): number {
  const output = execSync('npm outdated --json', { encoding: 'utf-8' });
  const outdated = JSON.parse(output);
  return Object.keys(outdated).length;
}

// Track metrics over time
const metrics = measureDebt();
console.log('Debt Metrics:', metrics);
// Log to tracking system (Datadog, Grafana, spreadsheet)

Trend tracking

# Debt Metrics Trend

| Month    | Complexity | Duplication | Coverage | TODOs | Outdated Deps |
|----------|------------|-------------|----------|-------|---------------|
| 2026-01  | 8.2        | 12.5%       | 65%      | 47    | 8             |
| 2026-02  | 7.8        | 11.2%       | 68%      | 42    | 6             |
| 2026-03  | 7.5        | 10.8%       | 72%      | 38    | 5             |
| 2026-04  | 7.1        | 9.5%        | 75%      | 35    | 4             |
| 2026-05  | 6.8        | 8.2%        | 78%      | 30    | 3             |
| 2026-06  | 6.5        | 7.5%        | 82%      | 25    | 2             |

Trend: All metrics improving. Duplication down 40%, coverage up 17 points.

Communicating Debt to Stakeholders

# Technical Debt Report — Q2 2026

## Executive Summary
Technical debt costs approximately 15 engineering days per sprint in
slower feature delivery. We recommend allocating 20% of Q3 capacity
to debt paydown, targeting the top 5 items.

## Top 5 Debt Items (by business impact)

1. **No payment integration tests** (TD-002)
   - Risk: Payment bugs reach production (happened 2x this quarter)
   - Fix: 3 days of test engineering
   - ROI: Prevents ~8 hours of manual testing per release

2. **Monolithic auth module** (TD-001)
   - Risk: Blocks multi-tenant revenue ($50k MRR at stake)
   - Fix: 5 days of refactoring
   - ROI: Unblocks Q3 multi-tenant launch

3. **Outdated Node.js 16** (TD-004)
   - Risk: End-of-life Sep 2026, security vulnerabilities
   - Fix: 2 days of upgrade + testing
   - ROI: Security compliance, performance improvements

4. **No CI/CD pipeline** (TD-005)
   - Risk: Manual deployments take 3 hours, 1 production incident per month
   - Fix: 4 days of DevOps work
   - ROI: Deployments in 15 minutes, fewer incidents

5. **Missing API documentation** (TD-006)
   - Risk: Partner integrations take 2 weeks instead of 2 days
   - Fix: 3 days of documentation
   - ROI: Faster partner onboarding

## Recommendation
Allocate 17 days (20% of Q3 capacity) to resolve items 1-5.
Expected ROI: 30% faster feature delivery in Q4.

Best Practices

  • For a deeper guide, see Clean Code: Naming, Functions, Classes, Comments.

  • Make debt visible — maintain a debt register that everyone can see

  • Quantify the interest — measure how much debt costs per sprint

  • Prioritize by business impact — not by what’s easiest to fix

  • Stop the bleeding first — prevent new debt before paying old debt

  • Allocate fixed capacity — 15-20% per sprint or one debt sprint per quarter

  • Follow the Boy Scout rule — leave code better than you found it

  • Track metrics over time — complexity, duplication, coverage, TODOs

  • Communicate in business terms — talk about cost, risk, and ROI, not code quality

  • Celebrate debt paydown — recognize engineers who reduce debt

  • Don’t go into debt for non-urgent features — deliberate debt should be rare and documented

Common Mistakes

  • Ignoring debt until it’s too late: debt compounds. A 1-day shortcut becomes a 5-day fix in 6 months. Track and pay early.
  • No debt register: invisible debt is unmanageable. Write it down, even in a simple markdown file.
  • Paying debt only when convenient: if you wait for free time, it never comes. Schedule debt paydown explicitly.
  • All-or-nothing refactoring: trying to fix everything at once. Small, continuous improvements are more sustainable.
  • Not measuring debt: without metrics, you can’t show progress. Track complexity, coverage, and duplication monthly.
  • Technical jargon with stakeholders: saying “cyclomatic complexity is too high” doesn’t motivate investment. Say “features take 30% longer to build.”

FAQ

What is technical debt?

The cost of shortcuts taken during development. Like financial debt, it accrues interest: each shortcut makes future changes slower and riskier. Technical debt includes poor design, missing tests, outdated dependencies, and lack of documentation.

How much time should we spend on technical debt?

15-20% of each sprint is a common allocation. Alternatively, dedicate one sprint per quarter entirely to debt paydown. The right amount depends on how much debt you have and how fast you’re accumulating new debt.

How do I convince management to invest in debt paydown?

Translate debt into business terms: slower feature delivery, more bugs, higher turnover, missed deadlines. Show the ROI: “Investing 3 days in tests saves 8 hours of manual testing per release.” Use past incidents as examples of debt cost.

What is the Boy Scout rule?

“Always leave the code better than you found it.” When you touch code for a bug fix or feature, make one small improvement: rename a variable, extract a function, add a test. Over time, these small improvements compound.

Should we ever take on deliberate technical debt?

Yes, when speed is critical and the debt is documented. Examples: a demo for a key customer, a time-sensitive market opportunity. Document the debt in the register, note the expected paydown date, and schedule it. Unplanned, undocumented debt is the problem.