Feature Flags: The Switch Behind Every Safe Migration
Feature flags make a live migration reversible: the four flag types, build vs. buy, fail-safe defaults and cleanup, from an engineer who led one.

On this page · 7 sections
- What are feature flags?
- How do feature flags make a migration reversible?
- Which kinds of flags exist: release, ops, experiment and permission?
- Should you build feature flags or use a service?
- How do you keep flags from becoming technical debt?
- How do feature flags work with the strangler fig pattern?
- Want a second opinion on your migration?
Key takeaways
- A flag separates deploying code from releasing it to users.
- Flags make code paths reversible, but they do not make data changes reversible.
- There are four common kinds: release, ops, experiment and permission. Each has a different lifespan.
- Start with something simple, and move to a service when non-engineers or many services need control.
- Every flag needs an owner and a removal date, or it becomes technical debt.
A feature flag is a runtime condition in your code that decides which behavior runs, so you can change it without deploying again. In a migration, feature flags are the switch that sends users to the old or the new implementation, which means a bad release can usually be undone by flipping a value instead of waiting for a redeploy.
I relied on this at avanzzada, where I led the full migration of a platform that applies to job openings automatically for its users. We moved its whole architecture from a legacy stack to a new one while people kept using it every day. This guide covers what I would want to know before starting.
What are feature flags?
A feature flag, also called a feature toggle, is a check in your code such as "if the new checkout is on, use it; otherwise use the old one." The important part is that the answer does not live in the code. It lives in a config file, a database table or a flag service, so changing it does not require a new deploy.
That separates two things teams often treat as one: deploying and releasing. You can ship new code to production switched off, test it with internal users, and then expose it gradually.
A flag can be evaluated in different ways:
- Globally, for everyone at once.
- Per user or per account.
- For a percentage of traffic.
- Per environment, such as staging or production.
How do feature flags make a migration reversible?
Without flags, a migration step is a one-way door. You deploy, watch the dashboards, and if something breaks you roll back by redeploying, which is slow and stressful. With a flag, rollback is flipping a switch.
If the codebase is one I just inherited, flags are not my first step. In the first weeks of a takeover I go in a fixed order: access first, then the build, then the money paths, then the risks. Only once I can build and deploy reliably do I start adding switches.
From there, my rule is to put the switch in front of everything before writing new code. Then the sequence looks like this:
- Add the flag and route everyone to the old path.
- Build the new path behind the flag, switched off.
- Where possible, run the new path in the background and compare its results with the old one, while users still get the old result.
- Turn it on for internal users.
- Turn it on for a small group of real users and watch errors and business metrics.
- Widen the group step by step until everyone is on the new path.
- Keep the old path for a stability window, then delete it.
What flags cannot reverse
A flag reverses which code runs. It does not reverse data. If the new path writes to a new database or changes a schema, flipping the flag back leaves the old system without those writes unless you keep both in sync. Plan that part separately, and be most careful on money paths such as payments, billing and checkout. Having worked on the cart and checkout team at Total Wine & More, I treat those paths with extra caution: small groups, long observation, and a clear definition of what "broken" means before you start.
Which kinds of flags exist: release, ops, experiment and permission?
Not all flags are the same, and mixing them up is how flag systems get messy. A common way to sort them:
| Type | Purpose | Typical lifespan | Migration example |
|---|---|---|---|
| Release | Hide unfinished or newly migrated work, then roll it out gradually | Days to weeks | Send a small share of users to the new service |
| Ops | Control system behavior under load or failure | Can be long-lived | A kill switch that returns traffic to the legacy path |
| Experiment | Compare variants to measure an outcome | Weeks, until a decision | Test whether the new flow converts the same as the old one |
| Permission | Give features to specific users or plans | Often permanent | Early access for a pilot customer |
In migrations, release flags and ops flags do most of the work. Release flags move traffic. Ops flags are the emergency brake. Experiment and permission flags matter less, but permission flags are handy for letting a friendly customer try the new path first.
The lifespan column matters. Release and experiment flags should be temporary. Ops and permission flags may stay, but they should be labeled as permanent so nobody wonders whether they can be deleted.
Should you build feature flags or use a service?
Both are reasonable. The right answer depends on who needs to flip flags and how many systems read them.
When building it yourself is enough
For one application and a handful of flags, a database table or config file with a small helper function works. It is cheap and has no new vendor. As a rule of thumb (my estimate, not a standard), this holds while only engineers change flags and you rarely need percentage rollouts.
When a service earns its place
Consider a service when you need:
- Gradual percentage rollouts and targeting by user attributes.
- An audit log of who changed what and when.
- Non-engineers, like support or product, to change flags safely.
- The same flags read consistently by several services.
Options include commercial products like LaunchDarkly and open-source ones like Unleash, Flagsmith and GrowthBook. Check current pricing and hosting models on their own sites, since they change.
Whichever you choose, design it to fail safe. In my own projects, if the flag store is unreachable, the code falls back to a known default, usually the old path. Keep evaluation fast and cache values, so a flag check never becomes the thing that slows down or takes down your app.
How do you keep flags from becoming technical debt?
Every flag creates at least two code paths, and every extra path has to be understood, tested and eventually removed. Flags left behind after a rollout are a common source of confusing code and surprise bugs.
These habits keep that under control:
- Give each flag an owner and a removal date when you create it, not later.
- Create the cleanup task at the same time as the flag, so removal is planned work. I open that ticket in the same pull request that adds the flag.
- Name flags by purpose, not by a vague project label.
- Avoid nesting flags inside other flags. Combinations multiply fast.
- Remove the old code, not just the flag, once the new path has been stable at 100% for your chosen window.
- Review the list regularly. Sorting flags by age shows what is stale.
- Test both states on critical paths, but do not try to test every combination.
Treat flag removal as part of the migration, not as optional cleanup. The migration is not done until the old path and its flag are gone.
How do feature flags work with the strangler fig pattern?
The strangler fig pattern replaces a live system piece by piece. A routing layer sits in front of the old system, and slices of functionality move to the new one over time until the old system has nothing left to do.
Flags are the control mechanism for that routing layer. The router or proxy decides which system serves a given route, and a flag decides who gets the new version of that slice. You can move one endpoint or screen at a time, send a small group through the new version, and widen it as confidence grows.
This is the same principle I followed at avanzzada. The platform stayed live and users kept relying on it daily, so each change had to be small enough to watch and easy to take back. A big-bang rewrite offers no such exit. A rewrite also tends to cost more and take longer than an incremental migration, because the old system has to keep running in parallel anyway.
Want a second opinion on your migration?
If you are planning a migration and want to sanity-check how you would roll it out and roll it back, email me@filipeeduardo.dev with the context. I am glad to compare notes and tell you what I would check first.
Frequently asked questions
Are feature flags the same as canary deployments?
No, but they work well together. A canary deployment sends a new version of the software to a few servers or users first. A feature flag controls behavior inside the code, regardless of which version is deployed. Flags let you do a canary-style rollout of a single feature.
Do feature flags slow down an application?
A well-built flag check is cheap, especially if values are cached locally. Problems come from calling a remote service on every request without caching, or from evaluating dozens of flags in a hot path. Measure it if you are worried.
How many feature flags are too many?
There is no universal number. A better test is whether each flag has an owner, a purpose and a removal date. If nobody can explain what a flag does, you already have too many.
Can feature flags replace staging or testing?
No. Flags reduce the risk of releasing, but they do not replace automated tests or a staging environment. They add a safety net after those checks, because production behaves differently from any test setup.

