Photo via Unsplash
Every engineering team has a version of the same horror story. The codebase was fine five years ago. Then features piled up, shortcuts were taken under deadline pressure, and the original developers left. Now every change is a negotiation with a system nobody fully understands.
The instinct is always the same: let’s just rewrite it.
And that instinct has killed more projects than the legacy code ever could.
What Is the Strangler Fig Pattern?
The Strangler Fig is named after a tropical plant that grows around a host tree, gradually replacing it while the original tree is still alive and functional. Martin Fowler introduced the concept in 2004, and it’s since become the gold standard for legacy migration.
The core idea: never shut down the old system to build the new one. Instead, you route specific requests or features to the new implementation while the legacy system handles everything else. Over time, the new system handles more and more traffic until the legacy code can be safely deleted.
Why Full Rewrites Fail (Again and Again)
Before diving into implementation, it’s worth understanding the failure modes. Rewrites fail for predictable reasons:
You underestimate what the old system actually does. Legacy systems accumulate decades of business logic — much of it undocumented, some of it in comments, some of it known only to employees who left years ago. When you start fresh, you discover this knowledge gap the hard way: in production, when something breaks that nobody thought to replicate.
The target keeps moving. While your team spends 18 months building the new system, the business keeps running on the old one. New features get added to the legacy system because they have to be. Now you have two moving targets.
You can’t test parity. Even with comprehensive test suites, proving that the new system behaves identically to the old one under all conditions is functionally impossible. There are always edge cases.
The Four-Layer Strangler Fig Architecture
A production-grade Strangler Fig migration typically involves four layers working in concert:
Legacy vs. Strangler Fig vs. Rewrite: The Real Comparison
| Factor | Keep Legacy | Strangler Fig | Big-Bang Rewrite |
|---|---|---|---|
| Business continuity | ✅ No disruption | ✅ No disruption | ❌ Long freeze |
| Risk level | 🟡 Grows over time | 🟢 Low, incremental | 🔴 Very high |
| Time to first value | 🟡 Immediate but diminishing | 🟢 Weeks | 🔴 12–24 months |
| Knowledge preservation | ✅ Implicit | ✅ Explicit via tests | ❌ Often lost |
| Team morale | 🔴 Degrades | 🟢 Improves steadily | 🟡 Drops during crunch |
| Cost predictability | 🟡 Reactive | 🟢 Phased and known | 🔴 Almost always over budget |
Based on patterns observed across legacy modernization engagements
The Migration Sequence That Works
After running multiple Strangler Fig migrations, we’ve converged on a sequence that minimizes surprises:
-
Characterization testing first. Before touching anything, write tests that document the current behavior of the system — including the bugs. These aren’t tests of what the system should do, they’re tests of what it does do. Michael Feathers calls these “characterization tests” in Working Effectively with Legacy Code.
-
Instrument the legacy system. Add logging and observability to understand actual usage patterns. You’ll discover that 40% of the code paths are never hit in production. That changes the migration priority order significantly.
-
Identify the seams. A “seam” is a boundary where you can change behavior without modifying surrounding code. These are your migration insertion points. In a monolith, seams are often service class boundaries or database table groups.
-
Build the facade, then migrate module by module. Never migrate two modules at the same time. The overhead of managing parallel bugs in two active migration streams is brutal.
Common Mistakes We See (And How to Avoid Them)
Mistake: Migrating the database too early. The database is the hardest thing to migrate and the most dangerous to get wrong. We almost always recommend migrating the application layer first and keeping the same database, then migrating the data model as a separate phase.
Mistake: Skipping the parallel-run phase. It’s tempting to flip the switch as soon as the new module passes tests. Don’t. Run new and old in parallel, compare outputs, and only switch over after a defined validation period.
Mistake: Treating it as a purely technical project. Strangler Fig migrations succeed when the business understands and supports the approach. If product teams keep adding to the legacy system because “the new one isn’t ready yet,” you end up chasing a moving target.
Conclusion
The Strangler Fig pattern isn’t glamorous. You won’t get to throw everything away and start fresh. But it consistently delivers what rewrites promise and rarely deliver: a modernized system, running in production, without the 18-month nightmare.
The pattern works because it respects a fundamental truth about software: running systems contain knowledge that isn’t written anywhere else. The goal is to extract and preserve that knowledge, not discard it.
Start small. Start with the seam that’s clearest. Get one module into the new system, learn from it, and use that momentum to tackle the next one.