Technical Debt: Future Cost Created by Today's Choices
📑 On this page
- A concrete example: duplicated validation
- Debt is not simply bad code
- Deliberate and accidental debt
- Interest payments
- Principal and interest
- Architecture debt
- Test debt
- Operational debt
- Data debt
- Dependency debt
- Debt registers
- Prioritize debt with evidence
- Repayment strategies
- Rewrites are risky
- Preventing unnecessary debt
- Communicating debt
- When accepting debt is rational
- Knowledge check
- The one idea to remember
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
- Why is imperfect code not automatically technical debt?
- What are principal and interest in the debt metaphor?
- Give one example of operational debt.
- Why can a rewrite create new risk?
- 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.