When to use feature flags (and when not to)
Feature flags are powerful. They are also overused. This post is a decision checklist.
Use them for: gradual rollouts
The canonical use case. You ship a feature to 1% of users, watch the metrics, ramp to 10%, then 50%, then 100%. If something breaks, you roll back the flag, not the deploy.
// Flag configured at 10% rollout in production
const useNewCheckout = flagify.isEnabled('new-checkout-flow');
Use them for: kill switches
Any feature that could take down production in a weird state deserves a kill switch. Payment processing. Email sending. Anything that hits a third-party service that goes down at 3am. The flag stays in the codebase forever. You hope to never flip it.
if (!flagify.isEnabled('enable-stripe-checkout')) {
return showMaintenanceBanner();
}
Use them for: trunk-based development
Merge incomplete features to main behind a flag that defaults to off. This kills long-lived feature branches and the merge conflicts they create. It also lets you deploy continuously even when half the team is mid-feature.
Use them for: entitlements
Pro users see the export button. Free users don’t. This is a targeting rule, not a release flag, so it lives as long as the business rule lives.
Use them for: infrastructure migrations
Swapping a database, moving from REST to GraphQL, replacing a legacy service. Put the new path behind a flag, send 1% of traffic to it, compare results. This is how teams migrate without downtime.
Do not use them for: static config
If a value changes once a year and requires a deploy anyway, put it in a config file. Flags add complexity. Don’t pay for complexity you don’t need.
Bad flag: max-upload-size. Good flag: something that changes based on user attributes or rollout percentage.
Do not use them for: secrets
Use a secrets manager. Vault, AWS Secrets Manager, 1Password, whatever. Flags are not encrypted at rest in most tools and they are not designed for rotation.
Do not use them for: every single if/else
The temptation is to wrap every conditional in a flag “just in case we want to change it later.” Don’t. Every flag is a branch in your code that must be tested, a value you must track in the flag dashboard, and a cleanup task for future you. Flags have a carrying cost.
The rule I use: would we want to flip this at runtime, without a deploy? If yes, flag. If no, plain conditional.
Gray area: experiments
A/B tests can be flags or they can be a separate experimentation tool. Both work. Flag-based experiments are simpler to set up and good for binary variants. Dedicated experimentation tools (Statsig, Optimizely) are better if you need statistical rigor, power calculations, and automatic variant assignment at scale.
If you’re starting out, a flag is fine. If you’re running dozens of experiments a month, you want a real experimentation platform.
Gray area: dark launches
Deploying code that is fully off, running only to warm caches or exercise infrastructure. This is legit but it is not really what flags are for — you could do the same thing with a plain boolean. The flag is mostly there so you can turn the thing on without a deploy when you are ready.
Heuristic
If the flag answers one of these questions, it probably belongs:
- Should this feature be on or off in production right now?
- Should this specific user see this feature?
- Am I rolling this out gradually?
- Do I need to kill this feature in under 60 seconds if it breaks?
If it doesn’t answer any of those, it’s probably something else — config, a role check, or a plain conditional.
Want to try feature flags without the overhead? Flagify is built for developers who want flags, targeting, and environments without a two-week onboarding process. See the quick start or read feature flag best practices to learn how to manage flag lifecycle properly.
Start for free — no credit card required.