# Tech Debt

> Treat tech debt like a loan: know which kind you took on, track the interest you pay, budget a steady share to repay it, and modernize with strangler figs.

Tech debt isn't a moral failing. It's a loan: you ship sooner now and pay interest later, in slower changes, more incidents and harder onboarding. Taking a loan on purpose is often the right call. Not knowing you have one is the problem.

## Know Which Kind You Have

Martin Fowler's [technical debt quadrant](https://martinfowler.com/bliki/TechnicalDebtQuadrant.html) splits debt by two questions: did you take it on deliberately, and was it prudent?

| | Reckless | Prudent |
| --- | --- | --- |
| **Deliberate** | Skipping design because there's "no time" | Shipping now, knowing the cost, with a plan to pay it back |
| **Inadvertent** | Not knowing good design in the first place | Learning, after building it, how it should have been built |

- **Deliberate and prudent** is a business decision. Record it as a backlog item with what it'll take to pay back, so the loan has terms. If the shortcut departs from the playbook, it also gets an [ADR](https://www.syntaqx.com/docs/development/design-docs), because that's exactly the choice someone will later mistake for an accident.
- **Inadvertent and prudent** is inevitable. You learn the domain by building in it. Pay it down as understanding improves.
- **Reckless** debt in either row is a process problem. Fix the process (reviews, design docs, mentoring), not just the code.

## Make It Visible

Debt nobody can see doesn't get prioritized. Every known piece is a tracked item in the normal backlog, and each one says what the interest is:

- **Incidents** it caused or made worse, linked from the [postmortem](https://www.syntaqx.com/docs/operations/incident-response).
- **Slower delivery:** "every change to billing takes three extra days because of X."
- **Risk:** an unsupported runtime, a dependency with no maintainer, a single point of failure.

Debt described as interest gets prioritized against features. Debt described as "the code is ugly" doesn't, and shouldn't.

## Budget for It Continuously

Reserve a steady share of every team's capacity for paying down debt and keeping things current, and spend it every cycle. I start around 20%. The exact number matters less than its being fixed: a share that's renegotiated every planning meeting loses to every feature, because each feature has a sponsor and the debt doesn't.

- **Not debt sprints.** A quarterly "cleanup sprint" lets interest compound for three months, then gets cancelled the first time a deadline slips.
- **The team chooses** what to spend it on. They know where the interest hurts.
- **Upgrades come out of this budget,** before they become emergencies. See [Dependencies & Upgrades](https://www.syntaqx.com/docs/development/dependencies).
- **It's engineering's share of the roadmap,** set before features are prioritized. [Roadmaps & Capacity](https://www.syntaqx.com/docs/product/roadmaps) covers what else it pays for.

## Pay It Down Where You're Working

The cheapest time to fix code is when you're already changing it: the context is loaded and the reviewer is already looking. Leave it a little better than you found it, with the refactor landed as its own pull request before the behavior change ([one concern per PR](https://www.syntaqx.com/docs/development/commits-and-reviews)). Improve what you touch; a drive-by rewrite of an unrelated module belongs in its own change.

## When to Stop and Pay

Some debt can't wait for the budget:

- It's **blocking a priority:** the feature can't be built safely on the current design.
- It's **causing incidents:** the same root cause shows up in postmortems twice.
- It's a **security or support deadline:** an end-of-life runtime, a vulnerable dependency with no patch.

At that point it becomes the priority, planned and staffed like any other project.

## Rewrite or Strangle

Big rewrites are the riskiest way to repay debt. They stop delivery for months, rebuild years of edge cases from memory, and ship all at once. The old system's ugliest code is often its most valuable: each odd branch is a bug someone hit in production and fixed, and a rewrite rediscovers them one incident at a time.

Prefer the **[strangler fig](https://martinfowler.com/bliki/StranglerFigApplication.html):** build the replacement beside the old system, route one capability at a time to it, and delete the old code as each piece moves. Every step ships, every step can be rolled back, and the work can pause without leaving anything half-done. The same idea works at every scale, from a service down to a module, and it's how [Zero-Downtime Migrations](https://www.syntaqx.com/docs/database/zero-downtime-migrations) handle schemas.

## Deprecate With Dates

Removing something is how debt actually goes away. Announce it with a date and the replacement, measure who still uses it and tell them directly, and delete it on the date; a deprecation without a removal date is just a comment. [Versioning & Deprecation](https://www.syntaqx.com/docs/api-design/versioning) is the public-API version of this.

## References

**In practice**

- [Martin Fowler: Technical Debt](https://martinfowler.com/bliki/TechnicalDebt.html), the debt metaphor and interest payments
- [Martin Fowler: Technical Debt Quadrant](https://martinfowler.com/bliki/TechnicalDebtQuadrant.html), deliberate vs inadvertent, reckless vs prudent
- [Martin Fowler: Strangler Fig](https://martinfowler.com/bliki/StranglerFigApplication.html), incremental replacement instead of a big rewrite

---

Canonical URL: https://www.syntaqx.com/docs/development/tech-debt
