Over time, every system accumulates design decisions that were fast or convenient at the time but that hinder future change. That gap between the ideal state of the code and its actual state is called technical debt, a financial metaphor coined by Ward Cunningham: just like monetary debt, it generates "interest" in the form of greater effort on every modification. Debt is not inherently bad —sometimes it is a sensible strategic decision— but ignoring it leads to paralysis. In this lesson we will study what it exactly is, how to classify it with Fowler's quadrant, how to measure it, and what strategies exist to pay it off, with continuous refactoring as the central practice.

Contents

  1. What technical debt is and the financial metaphor
  2. Fowler's technical debt quadrant
  3. Types and causes of technical debt
  4. How to measure technical debt
  5. Debt repayment strategies
  6. Continuous refactoring
  7. Common mistakes and tips
  8. Exercises
  9. Conclusion

  1. What technical debt is and the financial metaphor

Technical debt is the implicit future cost of rework caused by choosing a limited or quick solution now instead of a better approach that would take more time.

The financial metaphor has three key concepts:

  • Principal: the effort needed to fix the problem (refactor, redesign).
  • Interest: the extra cost you pay on every change while the debt exists (more slowness, more bugs).
  • Leverage: sometimes taking on debt lets you deliver value sooner and "pay it back" later; it can be a rational decision.
graph LR
    A[Quick decision] --> B[Technical debt]
    B --> C[Interest: each change costs more]
    C --> D{Is it paid?}
    D -->|Yes| E[Principal settled: healthy code]
    D -->|No| F[Compound interest: paralysis]

Explanation of the diagram: a quick decision creates debt; while the principal is not settled, interest accumulates on each modification. If it is not managed, the interest compounds until the system becomes almost unmodifiable.

  1. Fowler's technical debt quadrant

Martin Fowler proposed a quadrant that classifies debt along two axes: whether it was incurred deliberately or inadvertently, and whether it was prudent or reckless. This nuances the moral judgment of debt.

Reckless Prudent
Deliberate "We don't have time for design" "We ship now and accept the consequences"
Inadvertent "What's a layer?" "Now we know how we should have done it"

Interpretation of each quadrant:

  • Deliberate and reckless: you know how to do it right but decide not to out of laziness. The most dangerous.
  • Deliberate and prudent: a conscious, justified decision (ship earlier to validate the market), with a repayment plan. Acceptable.
  • Inadvertent and reckless: lack of knowledge or discipline; the team doesn't even know it is generating debt. Combated with training.
  • Inadvertent and prudent: the debt of natural learning; only upon finishing do you understand the domain. Inevitable and healthy.

The lesson of the quadrant is that prudent and managed debt can be a strategic tool; reckless debt is a problem of discipline or knowledge.

  1. Types and causes of technical debt

Type of debt Example
Code Duplicated code, huge methods, confusing names
Design/architecture High coupling, absence of layers, circular dependencies
Testing Insufficient coverage, fragile tests
Documentation Outdated or nonexistent documentation
Infrastructure Un-updated library versions, manual environments
Dependencies Outdated libraries with vulnerabilities

Common causes:

  • Deadline pressure and rushed deliveries.
  • Lack of knowledge or experience on the team.
  • Requirement changes that invalidate previous decisions.
  • Absence of time dedicated to maintenance.
  • Staff turnover and loss of context.

  1. How to measure technical debt

Debt is partly intangible, but quantifiable indicators exist. Static analysis tools (such as SonarQube) compute metrics and a technical debt ratio.

Common metrics:

  • Code smells and maintainability issues detected automatically.
  • Cyclomatic complexity: the number of independent paths through a method; the higher it is, the harder it is to test and maintain.
  • Code duplication (percentage).
  • Test coverage.
  • Technical Debt Ratio (TDR): estimated remediation cost divided by the development cost.
# Example of a quality gate configuration in SonarQube
sonar.qualitygate:
  conditions:
    - metric: new_coverage          # coverage of new code
      op: LESS_THAN
      error: 80                      # fails if <80%
    - metric: new_duplicated_lines_density
      op: GREATER_THAN
      error: 3                       # fails if >3% of duplicated lines
    - metric: new_technical_debt     # debt introduced by this change
      op: GREATER_THAN
      error: 60                      # fails if it adds more than 60 min of debt

Explanation: this quality gate prevents new code from degrading the project's health. Instead of requiring all historical debt to be cleaned up at once (unfeasible), it applies the "Clean as You Code" approach: new code must meet the standard, containing debt at its source.

Valuable indirect indicators:

  • Decreasing team velocity with no apparent cause.
  • Increasing defect rate.
  • Rising lead time (time from commit to production).

  1. Debt repayment strategies

Not all debt should be paid off, nor in the same way. Main strategies:

Strategy What it involves When to use it
Incremental repayment Improve a little with each change (Boy Scout Rule) By default, always
Dedicated refactoring Reserve sprint capacity to settle debt Localized, known debt
Partial rewrite Rewrite a specific module An unmanageable critical module
Full rewrite Redo the system Last resort, very risky
Tolerate the debt Consciously do nothing Stable code that isn't touched

Prioritization: pay off first the debt that is in areas of high change and high impact. Debt in code that is never modified generates little interest; it is not worth settling. A useful matrix crosses "frequency of change" with "pain it causes."

Management recommendations:

  • Make the debt visible (backlog, labels, a technical debt register).
  • Allocate a fixed capacity (e.g., 15-20% of each iteration) to maintenance.
  • Accompany every repayment with tests that protect the behavior.

  1. Continuous refactoring

Refactoring is improving the internal structure of the code without changing its external behavior. Continuous means doing it in small, constant steps, not in large, sporadic projects.

Golden rule: always refactor under the safety net of tests. Without tests, it isn't refactoring, it's risk.

// BEFORE: a long method, with several responsibilities and hard to follow
public double processOrder(Order p) {
    double total = 0;
    for (Line l : p.getLines()) {
        total += l.getPrice() * l.getQuantity();
    }
    if (p.getCustomer().isVip()) {
        total = total * 0.9;
    }
    if (total > 100) {
        total = total; // free shipping
    } else {
        total = total + 5;
    }
    return total;
}
// AFTER: extracting methods with expressive names
public double processOrder(Order p) {
    double subtotal = calculateSubtotal(p);
    double discounted = applyVipDiscount(p, subtotal);
    return applyShipping(discounted);
}

private double calculateSubtotal(Order p) {
    return p.getLines().stream()
            .mapToDouble(l -> l.getPrice() * l.getQuantity())
            .sum();
}

private double applyVipDiscount(Order p, double subtotal) {
    return p.getCustomer().isVip() ? subtotal * 0.9 : subtotal;
}

private double applyShipping(double total) {
    return total > 100 ? total : total + 5;
}

Explanation of the refactoring: we apply Extract Method to separate each responsibility (subtotal, discount, shipping) into a method with a clear name. The external behavior does not change —which is why it is safe if there are tests— but the main method now reads like a description of the process. This reduces code debt and cyclomatic complexity.

Frequent refactorings: extract method, rename, extract class, replace conditionals with polymorphism, introduce parameter object. Modern IDEs automate them safely.

Common Mistakes and Tips

  • Refactoring without tests. It is the most common cause of introducing bugs while "improving" the code. The safety net first.
  • Mixing refactoring with functional changes in the same commit. Separate them: if something fails, you won't know whether it was the improvement or the new feature.
  • Waiting for the "big refactor" or full rewrite. It almost always fails or never arrives. Debt is managed continuously and incrementally.
  • Treating all debt the same. Prioritize by impact and frequency of change; ignoring this wastes effort on dead code.
  • Not making the debt visible. What is not measured or recorded is not managed. Use the backlog and metrics.
  • Confusing refactoring with rewriting. Refactoring preserves behavior in small steps; rewriting starts from scratch and is far riskier.
  • Tip: adopt "Clean as You Code" to avoid degrading what is new, and the Boy Scout Rule to improve what you touch.

Exercises

Exercise 1. Classify the following situations in Fowler's quadrant: a) "We're launching the MVP with file-based persistence to validate the business, and we'll migrate to a DB if it works." b) "I copied and pasted the module because I didn't know interfaces existed."

Exercise 2. You have technical debt in two modules: one that is modified almost every week and another that hasn't been touched in two years. With limited resources, which do you prioritize and why?

Exercise 3. Refactor this method by extracting responsibilities (assuming tests exist):

public String generateGreeting(User u) {
    String s = "";
    if (u.getAge() < 18) s = "Hi there, young one "; else s = "Dear ";
    s = s + u.getName();
    if (u.isPremium()) s = s + " (premium customer)";
    return s;
}

Solutions

Solution 1. a) Deliberate and prudent: a conscious, justified decision, with a repayment plan (migrate if the business works). It is acceptable strategic debt. b) Inadvertent and reckless: generated through ignorance of the design. Combated with training and code review.

Solution 2. You prioritize the module modified every week. Debt generates "interest" on every change; in a high-change module that interest is paid constantly, so settling it has a high return. The module frozen two years ago generates little or no interest: tolerating its debt is the right choice as long as it doesn't need to be touched.

Solution 3. We extract each decision into a method with an expressive name:

public String generateGreeting(User u) {
    return salutation(u) + u.getName() + premiumBadge(u);
}

private String salutation(User u) {
    return u.getAge() < 18 ? "Hi there, young one " : "Dear ";
}

private String premiumBadge(User u) {
    return u.isPremium() ? " (premium customer)" : "";
}

The behavior is preserved, but the main method expresses the intent, and each rule is isolated and testable.

Conclusion

Technical debt is inevitable, but managing it is a decision. We have seen that the financial metaphor (principal and interest) explains why ignored debt becomes paralyzing, and that Fowler's quadrant helps us distinguish strategic debt from negligent debt. We learned to measure it with metrics and quality gates under the "Clean as You Code" approach, to prioritize its repayment according to impact and frequency of change, and to settle it through continuous refactoring under the protection of tests. With this lesson we close Module 2 on design principles and tactics: you now have a complete framework —from coupling and cohesion to debt management— for making conscious design decisions. The next module will make the leap to the architectural styles and patterns that structure complete systems.

Application Architecture Course

Module 1: Fundamentals of Application Architecture

Module 2: Design Principles and Tactics

Module 3: Architectural Styles and Patterns

Module 4: Distributed Architectures and Microservices

Module 5: Event-Driven Architectures and Messaging

Module 6: Domain-Driven Design (DDD)

Module 7: Data and Persistence

Module 8: Cloud Architecture and Deployment

Module 9: Quality, Security and Observability

Module 10: Evolution, Governance and Case Studies

© Copyright 2026. All rights reserved