Skip to content
Home » Software Design » Understanding Technical Debt — With C Examples

Understanding Technical Debt — With C Examples

Technical Debt featured image with purple background, Practical badge, TD icon, Recognize and Manage It subtitle in the Software Design in C series by NerdyElectronics
Software Design Principles in C
Part 26 of 31View Full Path →

KEY TAKEAWAYS

  • Technical debt is the future cost of today’s shortcuts — it accrues interest
  • Some debt is intentional and acceptable; uncontrolled debt is dangerous
  • Make debt visible through documentation and issue tracking
  • Pay it down incrementally with the Boy Scout Rule and dedicated refactoring time
  • Prioritize by pain: focus on high-traffic code that causes repeated problems

What Is Technical Debt?

Technical debt is the cost you pay later for taking shortcuts now. It is a metaphor borrowed from finance: just like financial debt, technical debt accrues interest. The longer you leave it, the more expensive it becomes to fix.Not all technical debt is bad. Sometimes taking a shortcut to meet a deadline is the right business decision — as long as you recognize the debt and plan to pay it back. The problem comes when debt accumulates silently until the codebase becomes unmaintainable.

Types of Technical Debt

1. Deliberate Debt — “We know this is a shortcut”

// TODO: Replace hardcoded IP with config file lookup
// Shipping demo tomorrow, will fix next sprint
#define SERVER_IP "192.168.1.100"
#define SERVER_PORT 8080

void connect_to_server(void) {
    tcp_connect(SERVER_IP, SERVER_PORT);
}
This is a conscious decision. The developer knows it is a shortcut and documented it. This is manageable debt.

2. Accidental Debt — “We didn’t know better”

// Written by someone learning C — works but poorly structured
float calc(int a, int b, int c, int d, int e, int f) {
    float x;
    if (a == 1) {
        x = b * c + d;
    } else if (a == 2) {
        x = b * c - d;
    } else if (a == 3) {
        x = (b + c) * d - e;
    } else {
        x = f;
    }
    return x;
}
// No one knows what a,b,c,d,e,f mean
// No one knows what a==1 vs a==2 means
This code works, but it is cryptic. Every developer who touches it will spend extra time understanding it. This is debt that accumulates through interest — slower development, more bugs.

3. Bit Rot — “It was fine when we wrote it”

// Written for 8-bit MCU with 256 bytes RAM
// Now running on 32-bit ARM with 512KB RAM
// But still uses global arrays of size 8 and manual bit packing
static uint8_t data_packed[8];  // 3 readings packed into 8 bytes

void store_reading(int idx, uint16_t val) {
    int byte_pos = (idx * 12) / 8;
    int bit_off  = (idx * 12) % 8;
    data_packed[byte_pos] |= (val >> (12 - bit_off));
    data_packed[byte_pos + 1] |= (val << bit_off);
}
The constraints that justified this complexity no longer exist. But the code remains because “it works.” Every new developer wastes hours understanding the bit packing.

How to Recognize Technical Debt

Warning Signs in Code

  • TODO/FIXME/HACK comments that are months or years old
  • Copy-pasted code — every copy is interest-bearing debt
  • Functions longer than 50 lines with multiple responsibilities
  • Global variables used to avoid proper parameter passing
  • Suppressed warnings#pragma warning(disable: ...)
  • Commented-out code that nobody dares to delete

Warning Signs in Process

  • “Don’t touch that file” — everyone avoids a module because changes cause cascading bugs
  • Onboarding takes weeks — new developers need extensive tribal knowledge to contribute
  • Simple features take disproportionate time — a “two-hour feature” takes two days because of tangled dependencies
  • Bug fixes create new bugstight coupling means changes propagate unpredictably

Managing Technical Debt

1. Make It Visible

// DEBT: Using global buffer instead of per-instance allocation.
// Impact: Cannot run multiple instances simultaneously.
// Effort to fix: Medium (2-3 hours, refactor to pass buffer as param)
// Priority: Fix before adding multi-channel support
static uint8_t g_buffer[256];
Document what the debt is, what its impact is, and how hard it is to fix. This turns invisible debt into a tracked issue.

2. The Boy Scout Rule

“Leave the code better than you found it.” When you touch a file to fix a bug or add a feature, spend 10 minutes improving the surrounding code — rename a variable, extract a function, replace a magic number. Small, continuous improvements prevent debt from accumulating.

3. Dedicated Refactoring Time

Allocate a fraction of each development cycle (10-20%) to paying down debt. This is not “gold plating” — it is maintenance that keeps the codebase healthy.

4. Prioritize by Pain

Not all debt needs to be paid immediately. Focus on debt that:
  • Is in code you change frequently (high-traffic areas)
  • Causes bugs repeatedly
  • Blocks new features
  • Makes onboarding difficult
Code you never touch can keep its debt — the interest rate is zero.

Example: Paying Down Debt

Before — Accumulated Debt

int p(int t, int m) {
    // magic formula from 2019, nobody remembers why
    if (t > 100) return m * 2 + 17;
    if (t > 50)  return m + 8;
    return m;
}
// Called from 15 places as p(temp, motor_speed)

After — Debt Paid

#define CRITICAL_TEMP    100
#define WARNING_TEMP      50
#define EMERGENCY_BOOST   17
#define WARNING_OFFSET     8

int calculate_motor_speed(int temperature, int base_speed) {
    if (temperature > CRITICAL_TEMP) {
        return base_speed * 2 + EMERGENCY_BOOST;
    }
    if (temperature > WARNING_TEMP) {
        return base_speed + WARNING_OFFSET;
    }
    return base_speed;
}
Same behavior. But now the next developer can understand, maintain, and safely modify this function.

Key Takeaways

  • Technical debt is the future cost of today’s shortcuts — it accrues interest
  • Some debt is intentional and acceptable; uncontrolled debt is dangerous
  • Make debt visible through documentation and issue tracking
  • Pay it down incrementally with the Boy Scout Rule and dedicated refactoring time
  • Prioritize by pain: focus on high-traffic code that causes repeated problems

Leave a Reply

Your email address will not be published. Required fields are marked *