Feature Flags and the Strangler Fig: Refactor Legacy Code Without the Big-Bang Rewrite
Every team has one: the module nobody wants to touch. It works, mostly. The tests are sparse. The original author left two years ago. And every quarter, somebody pitches the rewrite.
Six months later, the rewrite is half-finished, the old code is still in production, and you've shipped exactly zero features from that team. There is a better way.
Why Big-Bang Rewrites Fail
Legacy code is dense with edge cases nobody documented. The quirks aren't bugs — they're the accumulated answers to support tickets, regulatory questions, and customer requests that shaped the system over years. A clean-room rewrite throws all of that away and rediscovers it the painful way: in production.
The cutover compounds the risk. You build the new system in parallel, freeze the old one for a weekend, run the migration, and pray. If the new system has gaps, you're debugging on a live customer base with no fallback.
The Strangler Fig Pattern
Martin Fowler named the strangler fig after the tree that grows around its host until the host eventually rots away. Applied to software: build the new code path alongside the old, route a small slice of traffic to the new path, and grow that slice over weeks or months. When the new path handles 100% of traffic, you delete the old code.
The pattern only works if you can flip between paths instantly, per request, without redeploying. That's where feature flags earn their keep.
import featureflow from 'featureflow-client';
const client = featureflow.init(process.env.FEATUREFLOW_API_KEY!, {
user: {
key: userId,
attributes: { plan: user.plan, region: user.region },
},
});
export async function calculateInvoice(orderId: string) {
const useNewEngine = client.evaluate('billing-engine-v2').isOn();
if (useNewEngine) {
return billingV2.calculate(orderId); // new strangler path
}
return billingV1.calculate(orderId); // legacy host
}One conditional. Two implementations. The flag controls the ratio. Start at 1% of internal users, watch error rates and invoice totals, then ramp.
Make the Migration Boring
Run both paths in shadow first. Before you route a single user to the new code, call both implementations in parallel and compare outputs. Log the diffs. You'll find the undocumented edge cases before any customer does.
Slice by surface area, not by time. Don't aim for "rewrite finished by Q3". Aim for "route /api/invoices through v2 for plan=enterprise by next Friday". Each slice ships independently and earns confidence for the next one.
Keep the kill switch close. When billingV2 mis-rounds a currency conversion at 3am, you flip the flag off from the dashboard. The next request goes through the legacy path. You debug in the morning, not at 3am.
Delete the old code on a deadline. The strangler fig only works if the host actually dies. Add a ticket the day you reach 100% rollout: remove billingV1, remove the flag, remove the conditional. Otherwise the dual-path stays forever and you've added complexity without removing any.
Where This Fits
The pattern scales from a single function to a service boundary. Replacing an in-process module? Wrap the call site in a flag. Splitting a monolith into a service? Route a percentage of requests through the new service via the same mechanism. Migrating from one third-party API to another? Same shape.
Featureflow gives you the targeting, percentage rollouts, and instant kill switches that make the strangler fig safe in production. See featureflow.com or browse the docs to see how the SDK fits into your refactor.
#FeatureFlags#StranglerFig#Refactoring#LegacyCode#ContinuousDelivery#DevOps
Replace legacy code without the big-bang weekend
Start free with Featureflow — gradual rollouts and instant kill switches built for safe refactors.
Start Now (Free)Related Articles
Testing Code That Has Feature Flags: Strategies That Don't Explode Your Test Matrix
Add ten boolean flags and you have 1,024 versions of your app — in theory. Here's how to keep your test suite tractable: stub the SDK, pin variants per test, default to safe, and clean up alongside the flag.
Feature Flags for Mobile Apps: Ship Without Waiting on App Store Review
App store reviews take days. Bugs don't wait. Feature flags let mobile teams ship code continuously, gate features remotely, and kill broken behaviour — without a new release.
Feature Flags and Observability: How to Know If Your Rollout Is Working
Releasing to 10% of users without watching metrics is just gambling at a smaller scale. Here's how to connect flag evaluations to your observability stack — so you know when to expand, and when to pull back.