Process
Technical Debt
Technical debt is the cost of taking shortcuts. You ship quickly by cutting corners—skipping tests, using a hacky solution, leaving code messy. You pay the cost later when that shortcut makes the next feature slower to build, or it causes a bug, or new developers waste time understanding it. Like financial debt, technical debt has interest—compounding costs over time.
What Technical Debt Is
The term "debt" is apt. You borrow from the future to gain speed today. Later, you pay it back with interest.
The loan: Skip writing tests on this feature. We ship a week earlier.
The interest: Six months later, someone changes that code and breaks something. You spend 3 days debugging. Or a new team member adds to that code without understanding it, creating new bugs. Or you want to refactor it and can't because you don't have tests to verify it still works.
Sometimes the debt is worth it. You're racing to ship before a competitor. Or you're validating an idea and don't yet know if it's viable. Fast validation beats perfect code.
Sometimes it's not worth it. You're building long-term infrastructure. You should invest in quality upfront because the debt will compound for years.
Types of Technical Debt
Code debt: The code works but it's messy. Multiple long functions. Duplicate code. Comments that say "this is a hack." The functionality is there, but maintaining it is painful.
Architectural debt: The high-level design has problems. The system is tightly coupled when it should be modular. Data flows in convoluted ways. Adding new features requires touching 5 files when it should touch 1.
Test debt: There aren't enough tests. Or tests exist but they're brittle and break when you change unrelated code. You can't refactor confidently.
Dependency debt: Libraries are outdated. Security patches are unapplied. Migrating to newer versions would take time, so you postpone it.
Documentation debt: There's no architecture documentation. README is out of date. No one new can understand the system without spending a week learning it.
How Debt Accumulates
Timeline pressure: "We need this feature in 2 weeks." Tests take time. Refactoring takes time. You ship without them.
Changing requirements: You build a feature one way. Requirements change. You patch the code instead of refactoring. The original design gets worse.
Learning on the job: Junior developers don't know best practices. They write code that works but isn't idiomatic. Or they discover a better approach partway through but don't have time to refactor.
No code review: When code changes aren't reviewed, bad patterns slip in. They become precedent. Other developers copy them.
No refactoring time: Sprints are packed with features. There's no budget for cleanup. Code gradually becomes worse.
The Interest on Debt
Here's where debt becomes dangerous. The interest compounds.
Month 1: You skip tests on a feature. You ship a week early. This sprint, debt is positive.
Month 3: Someone finds a bug in that code. Debugging takes longer because there are no tests. Someone fixes it by adding a workaround instead of refactoring the underlying issue.
Month 6: A new feature overlaps with the old code. Developers spend time understanding the old code (which is poorly written with no tests). They add their feature, introducing bugs that are hard to debug without tests. Cycle repeats.
Month 12: The codebase is a mess. New features that should take a week take 3 weeks because developers navigate around bad code. Junior developers are frustrated and quit. The team moves slower and slower.
The interest is: each new feature is slower to build. Bug fixes take longer. Hiring is harder because the codebase is intimidating.
Measuring Technical Debt
How do you know if your codebase has high debt? Look at:
- Cycle time: How long from "idea" to "shipped"? If it's growing over time, debt is slowing you down.
- Defect rate: Are bugs increasing? High debt means more bugs. Are they the same bugs in the same areas? Bad code breeds bad code.
- Onboarding time: How long until a new developer is productive? If it's growing (used to be 1 week, now it's a month), the codebase is getting harder to understand.
- Developer morale: Do developers like working on this codebase? Or do they dread it? That's a sign of debt.
- Code coverage: What percentage of code is covered by tests? 90%+ is good. Below 50% is worrying.
- Dependency age: Are libraries updated regularly? Or are they years out of date?
Good Debt vs Bad Debt
Not all debt is bad. Sometimes shortcuts are right.
| Good Debt | Bad Debt |
|---|---|
| You know it's debt and you have a plan to pay it back | You ignore it and hope it doesn't matter |
| You take a shortcut on a small part of the system | You cut corners throughout the entire codebase |
| You're validating an unproven idea or racing to market | You're doing it because you don't know how to do it right |
| The debt is temporary (next sprint you'll clean it up) | The debt is permanent (you never come back to it) |
| You document the debt so future developers know about it | You hide it and let future developers discover it the hard way |
Paying Down Debt
Paying down debt takes discipline. It competes with feature work for time. But ignoring it is more costly.
- Refactoring: Rewrite messy code to make it cleaner. No behavior change. Takes time but reduces future interest.
- Adding tests: Go back to code without tests. Write tests for it. Slower now, but next change is faster.
- Dependency upgrades: Update libraries to latest versions. Takes time but keeps you on supported versions.
- Incremental improvement: Each time you touch a piece of code, improve it slightly. Don't rewrite it all at once—just make it a bit better.
- Rewrites: Sometimes a component is so broken that patching it is slower than rewriting. Rewrite it right. (But be careful—rewrites are risky and often overestimated.)
Communicating Debt to Non-Technical Stakeholders
Business people often don't understand technical debt. "The code works. Why do we need to change it?"
Frame it in business terms:
"Right now, it takes us 3 weeks to add a new feature. If we refactor the payment system (2 weeks), each future feature will take 2 weeks. We'll break even in 2 features."
Or: "We have no tests for the search feature. When we change it, we have to manually test 20 scenarios. It takes 2 days per change. Writing tests (3 days) means each future change is 2 hours. We'll break even in 5 changes, and we make changes weekly."
Or: "That library is 3 versions out of date. There's a security vulnerability. Updating now (1 day) is faster than a security incident later (3 days + reputation damage)."
Budgeting for Debt Repayment
Healthy teams allocate time for debt paydown in each sprint. Common approaches:
- 20% allocation: 80% of sprint capacity for features, 20% for debt paydown and maintenance
- One sprint per quarter: Every 4th sprint, the team does refactoring and maintenance only
- If-we-have-time: After committing to features, whatever capacity remains goes to debt (bad approach—features always consume all capacity)
The 20% approach is most reliable. It's predictable. Business knows capacity is reserved. And it prevents debt from building up to catastrophic levels.
Preventing Debt with Process
It's easier to prevent debt than pay it off. Build practices that keep debt from accumulating:
- Code review: When code is reviewed before merge, bad patterns are caught early. Reviews are less popular than writing code, but they save more time.
- Testing standards: Every pull request requires tests for new code. Code without tests gets blocked from merging.
- Linting and formatters: Automate style consistency. Every team member doesn't format code their own way.
- Documentation requirements: Major changes require documentation updates. You catch staleness before it becomes a problem.
- Refactoring time: Budget for cleanup. Don't let every sprint be features-only.
- Technical leadership: Have experienced developers guide architecture decisions. Their experience prevents debt at the design stage.
Debt in Inherited Codebases
Sometimes you inherit a codebase that's in debt. Someone else made the shortcuts. Now you own the cost.
What to do:
- Document the debt: Identify the biggest problems. Write them down. Communicate them to stakeholders.
- Don't panic. Yes, it's messy. But if it's in production and working, it's not an emergency. You have time to improve it.
- Start paying it down incrementally. Pick the most painful area. Spend a sprint improving it. Move to the next area.
- Prevent new debt. Set standards for new code. Code review everything. Test new features thoroughly. Don't let the codebase get worse while you improve it.
- Plan for the long haul. Inherited debt takes years to pay off. That's okay. Consistent improvement is better than a risky rewrite.
The Long-Term View
Technical debt is a trade-off. Sometimes going fast now is worth paying later. But the payment always comes due. The question is whether you can afford it.
A startup might take debt early to validate the market. Makes sense. But once the market is validated, you need to start paying it down or velocity will eventually stall.
A mature company needs healthy codebases. They can't take debt—they're building long-term. Quality investments pay dividends over years.
The key is being intentional. If you take debt, know it. Plan to pay it back. Don't let it compound into a catastrophe.
The next section covers Post-Launch—what happens in the critical first weeks after you ship, when you're monitoring, responding to bugs, and gathering user feedback.