← All posts
7 min read

Technical Debt: Future Cost Created by Today's Choices

#technology#software-design#technical-debt#engineering-management
📑 On this page

Software is never perfect, but not every imperfection is technical debt.

Technical debt is future cost created by a design or implementation choice that makes later change, operation, or understanding harder.

The debt metaphor is useful because a shortcut can produce value now while creating ongoing interest.

It becomes dangerous when the cost is invisible, compounds, or blocks important work.

A concrete example: duplicated validation

A team needs to ship a regulatory rule by Friday.

Instead of refactoring the shared validation system, it copies logic into three workflows.

The shortcut meets the deadline.

Future cost includes:

  • Every rule change must update three places.
  • Tests must cover each copy.
  • One copy may be missed.
  • Developers must discover which version is authoritative.

This is deliberate debt if the team understands and records the trade. It becomes unmanaged debt if everyone forgets why the duplication exists.

Debt is not simply bad code

Code can be:

  • Old but stable
  • Unfashionable but understandable
  • Verbose but reliable
  • Imperfect but cheap to change

These qualities do not automatically create debt.

A sophisticated design can create debt if nobody can operate it or every small change crosses many services.

Debt is about the future cost imposed on expected work, not aesthetic dislike.

Deliberate and accidental debt

Deliberate debt is accepted consciously:

  • Ship a simpler implementation to test demand.
  • Defer automation for a one-time migration.
  • Use a manual approval until volume justifies a workflow.

Accidental debt emerges through:

  • Missing knowledge
  • Changing requirements
  • Inconsistent changes
  • Tool limitations
  • Staff turnover
  • Unrecognized coupling

Both need management.

Shaming teams for all debt discourages honest discussion and hides reasonable tradeoffs.

Interest payments

Debt interest appears as:

  • Slower feature delivery
  • More defects
  • Longer onboarding
  • Fragile deployments
  • Repeated manual work
  • Increased incident risk
  • Difficulty updating dependencies
  • Excess infrastructure cost

One confusing module may add an hour to every change.

A missing test environment may turn each release into a risky all-day event.

Measure the recurring friction, not only the cleanup estimate.

Principal and interest

In the metaphor:

  • Principal: Work needed to remove or reduce the debt
  • Interest: Ongoing extra cost while it remains

A rewrite costing six months may have enormous principal.

If the affected system changes once a year and rarely fails, the interest may be low, making repayment a poor priority.

A smaller refactor in a module changed every day may provide greater return.

Prioritize using principal, interest, risk, and strategic importance.

Architecture debt

Architecture debt can include:

  • Shared database with unclear ownership
  • Synchronous dependency chains
  • One deployment for unrelated teams
  • No isolation for sensitive data
  • Platform tied to an unsupported runtime

Architecture debt has broad impact and expensive repayment.

It often requires staged migration rather than one refactor.

Fitness functions and decision records can prevent important boundaries from eroding silently.

Test debt

Test debt appears when:

  • Critical paths lack automated checks.
  • Tests are slow or flaky.
  • Test data is unrealistic.
  • Suites assert implementation details.
  • Environments differ from production.

The interest is slower feedback and lower confidence.

Simply adding more tests can increase debt if the tests are brittle.

Improve test architecture: stable boundaries, useful fixtures, focused layers, and ownership of failures.

Operational debt

Operational debt includes:

  • Manual deployment
  • No useful monitoring
  • Unknown service ownership
  • Untested backups
  • Credentials that cannot rotate
  • Alerts without runbooks
  • Repeated one-off production fixes

It may not appear in source code, but directly affects reliability and delivery.

An application can have elegant code and severe operational debt.

Include operators and incident history when assessing system health.

Data debt

Data debt includes:

  • Undefined fields
  • Duplicate sources of truth
  • Missing lineage
  • Inconsistent identifiers
  • Unbounded retention
  • Manual correction
  • Schemas no one owns

Data survives application rewrites and can be harder to repair.

A code bug can be fixed today; years of incorrectly categorized records may require expensive reconstruction.

Schema, metadata, quality monitoring, and migrations deserve first-class investment.

Dependency debt

Deferring library and runtime updates can create a widening gap.

Interest includes:

  • Security exposure
  • Incompatibility
  • Unsupported tooling
  • Harder eventual migration
  • Fewer people familiar with the version

Continuous small upgrades are often cheaper than one multi-year jump.

Not every new release should be adopted immediately. Maintain a supported, tested update policy based on risk and compatibility.

Debt registers

Record significant debt with:

  • Description
  • Cause
  • Affected area
  • Current interest
  • Risk
  • Repayment options
  • Owner
  • Trigger or review date

Avoid an enormous unactionable backlog of every code smell.

The register should support decisions, not become a museum of complaints.

Link debt to delayed work, incidents, cost, or quality impact.

Prioritize debt with evidence

Useful signals include:

  • Frequency of change
  • Defect rate
  • Incident history
  • Developer wait time
  • Support load
  • Performance or cost
  • Security exposure
  • Upcoming business roadmap

A module that blocks the next three strategic features deserves attention.

Debt in a stable system scheduled for retirement may be accepted and contained.

Context determines value.

Repayment strategies

Debt can be reduced through:

  • Opportunistic refactoring during feature work
  • Dedicated migration projects
  • Strangler-pattern replacement
  • Automation
  • Tests around risky behavior
  • Interface stabilization
  • Documentation
  • Component retirement

Do not require a complete rewrite when incremental containment works.

Define the desired outcome:

  • Reduce change time
  • Eliminate duplicate rules
  • Support current runtime
  • Restore independently

"Clean up the code" is too vague to measure.

Rewrites are risky

A rewrite promises a fresh start but must rediscover:

  • Hidden business rules
  • Edge cases
  • Operational behavior
  • Integrations
  • User workarounds

Meanwhile, the old system continues to change.

Rewrites can be justified when architecture fundamentally blocks goals and incremental migration is more expensive.

Use staged replacement, compatibility tests, data migration plans, and explicit scope.

New code can acquire debt before launch if old lessons are not preserved.

Preventing unnecessary debt

Helpful practices include:

  • Clear requirements
  • Small reviewed changes
  • Automated tests
  • Continuous integration
  • Regular dependency updates
  • Architecture boundaries
  • Observability
  • Retrospectives
  • Time for maintenance

Prevention is not zero shortcuts.

It is choosing shortcuts consciously, limiting blast radius, and creating a repayment or containment plan.

Communicating debt

Translate debt into outcomes:

Instead of:

The module is messy.

Say:

Every pricing change requires updates in four copies, adds two days of testing, and caused three production discrepancies this quarter.

This makes the investment comparable with feature and risk work.

Engineering leaders should avoid using debt as a catch-all excuse. Show evidence, alternatives, and expected return.

When accepting debt is rational

Debt can be acceptable when:

  • The learning value is high.
  • The system is temporary.
  • Time-to-market is critical.
  • Impact is contained.
  • Repayment trigger is known.
  • Interest is low.

A prototype does not need production architecture.

But temporary systems often become permanent. Mark assumptions, isolate shortcuts, and set review dates.

Responsible debt is visible debt.

Knowledge check

  1. Why is imperfect code not automatically technical debt?
  2. What are principal and interest in the debt metaphor?
  3. Give one example of operational debt.
  4. Why can a rewrite create new risk?
  5. How should debt be communicated to nonengineering stakeholders?

The one idea to remember

Technical debt is a trade across time. Make the future cost visible, measure its interest, prioritize it against real goals and risks, and repay or contain it when the return justifies the work.