Tech Debt & Stack Migrations
โฑ๏ธ ~4-minute bite ยท solve the sandbox to master
5-Year-Old Metaphor
โ The physical, real-world picture. No jargon.๐ณ Tech debt = credit card debt. Some is strategic. All of it has interest. The strangler fig = renovating a building while it's occupied.
The debt metaphor
Ward Cunningham coined "technical debt" to explain why rushed code is a borrowing: you get speed now and pay in slower future development. The interest is paid every time a developer touches that area โ extra time to understand, work around, or fix accidental complexity.
Some debt is strategic: you know a better approach but choose the fast one with a explicit payback plan. That's like using a mortgage โ not inherently bad. Reckless debt โ taking shortcuts without awareness or plan โ is like credit card debt at 30% APR.
When to migrate now
- โข Debt is slowing every feature in that area
- โข Security or compliance risk
- โข Key dependencies end-of-life
- โข On-call burden is unsustainable
When incremental is fine
- โข Code works, just inelegant
- โข Area rarely touched
- โข No clear migration target yet
- โข Team has more urgent priorities
Interactive Sandbox
โ Move something, see it react instantly.Migration Strategy
Risk
Low โ framework only
Timeline
Ongoing
Steps
5
Migration steps
- 1.Deliberate + Reckless: 'We don't have time to design this properly' โ pure negligence. Avoid.
- 2.Deliberate + Prudent: 'Ship now, we know we'll refactor the auth layer in Q3' โ strategic debt. OK if actually paid back.
- 3.Inadvertent + Reckless: 'What's layering?' โ lack of knowledge or skill. Address through education and code review.
- 4.Inadvertent + Prudent: 'We now know a better approach' โ learning debt. Refactor when you encounter it.
- 5.Decision: Is this deliberate? Is it prudent? Reckless debt is always wrong. Prudent deliberate debt may be strategic โ but write down the payback plan now.
Challenge
Visit all 5 migration patterns. Understand which strategy to use for different migration scenarios.
Why Should I Care?
โ The exact interview question + the bug it kills.Interview questions
Q: What is Martin Fowler's technical debt quadrant?
Fowler's quadrant has two axes: Deliberate/Inadvertent and Reckless/Prudent. Deliberate+Prudent: conscious strategic shortcut with a payback plan (acceptable). Deliberate+Reckless: "no time to design" (always bad). Inadvertent+Prudent: "we now know a better way" โ learning debt (refactor when encountered). Inadvertent+Reckless: "what's layering?" โ skill/knowledge gap (address through training and review).
Q: When is a full rewrite justified?
Almost never. Full rewrites are high-risk: you lose battle-tested bug fixes, undocumented edge case handling, and institutional knowledge embedded in the old code. The new system usually ships later than expected and recreates many of the same bugs. The Strangler Fig pattern is almost always preferable โ incremental replacement with constant validation. A rewrite is justified when: (1) the runtime/platform is being discontinued, (2) the old codebase is literally unmaintainable (no one alive understands it), (3) a complete domain model change makes incremental migration semantically impossible.
Q: How do you measure tech debt?
Proxy metrics: code duplication (SonarQube), cyclomatic complexity, test coverage by file, dependency age (npm outdated), time-to-onboard new engineers on a module, time-to-implement features vs estimate in high-debt areas. The most honest metric: ask engineers "which files do you dread touching?" โ that's where the debt is.
The Deep Dive
โ Spec refs, engine internals, the minutiae.jscodeshift codemod example
| 1 | // codemod.js โ rename a prop across all usages |
| 2 | // Before: <Button variant="primary" /> |
| 3 | // After: <Button intent="primary" /> |
| 4 | ย |
| 5 | export default function transformer(file, api) { |
| 6 | const j = api.jscodeshift; |
| 7 | return j(file.source) |
| 8 | .find(j.JSXAttribute, { name: { name: 'variant' } }) |
| 9 | .filter(path => { |
| 10 | // Only on Button components |
| 11 | return path.parent.value.name.name === 'Button'; |
| 12 | }) |
| 13 | .forEach(path => { |
| 14 | path.node.name.name = 'intent'; |
| 15 | }) |
| 16 | .toSource(); |
| 17 | } |
| 18 | ย |
| 19 | // Run: |
| 20 | // npx jscodeshift -t codemod.js src/ --extensions=tsx,ts --dry |
| 21 | // Remove --dry when satisfied with the diff |
Migration readiness checklist
DORA metrics and tech debt
High-debt codebases have measurable DORA metric degradation: longer lead times (harder to implement features), higher change failure rates (more bugs from unintended interactions), longer MTTR (harder to find and fix issues in complex code). Tech debt paydown shows up as improving DORA metrics โ this is the business case in engineering team language.
Interview Questions
โ Real questions from real interviews โ with answers.Strangler fig + feature flags let us migrate the checkout service over 8 weeks without a freeze.
Measure the interest rate: extra hours per sprint in high-debt areas vs low-debt areas.
Almost always โ strangler fig gives continuous validation; rewrites give you a second system that breaks in new ways.
Carve a seam, add tests on entry, and apply the Boy Scout Rule on every PR that touches it.
Ship at 0%, validate on internal users, roll out by segment, clean up after 2 weeks at 100%.
Deliberate/prudent debt is OK with a written payback plan; reckless shortcuts need to be caught in review.
Memory Game
โ Quick quiz โ lock the concept in long-term memory.What is the 'golden path' and how does it relate to tech debt prevention?