A feature toggle (flag) is a runtime switch that controls whether a feature is enabled. Lets you deploy code without releasing the feature.
Feature flags are essential for trunk-based development, gradual rollouts, A/B testing, and operational kill switches. Done well, they're a powerful tool. Done poorly, they accumulate as permanent technical debt.
Different uses; different lifecycles.
Wrap in-progress features. Enable for testing, gradual rollout. Remove after the feature is stable.
Lifetime: weeks to months.
Compare variants for product experiments. Statistical analysis decides winner.
Lifetime: weeks for the experiment.
Premium features, customer-specific access. Persistent state per customer.
Lifetime: indefinite (driven by business).
Quickly disable a problematic feature. Circuit breakers.
Lifetime: indefinite (kept for emergencies).
These are different tools. Treating them all the same causes problems.
if (featureFlag.isEnabled('new-checkout')) {
showNewCheckout();
} else {
showOldCheckout();
}
Simple, common.
const variant = featureFlag.variant('checkout-experiment');
if (variant === 'A') ...
else if (variant === 'B') ...
For experiments with multiple options.
Flags evaluated based on user, group, percentage:
if (featureFlag.isEnabled('new-feature', { userId, groups: user.groups })) {...}
The flag service evaluates rules: this user, this group, this percentage.
For small teams, a database table + simple service can suffice. The "build vs. buy" depends on scale and feature needs.
Unleash, GrowthBook, OpenFeature standardization.
Each flag should have:
The cleanup step is where most teams fail. Flags accumulate; old code paths persist; the codebase gets crufty.
Practices that prevent flag rot:
Every flag has an owner. The owner is responsible for retirement.
Set when created. "Remove by end of Q3 2026." Not a hard deadline but a reminder.
Quarterly: list all flags. Which are old? Owner explains why or removes.
When retiring a flag, the PR removes both the flag check and the deprecated code path. The "if/else" becomes one path.
Some flag services flag (heh) old flags as candidates for removal.
Start with internal users, then 1%, then 10%, then 100%. Watch metrics at each step.
For risky features, the kill switch is a flag that disables in <1 minute. Used in incidents.
For experiments, ensure a user always sees the same variant. Random per-call assignment ruins experiment quality.
Different defaults in dev, staging, production.
Flag evaluation can be expensive (network call). Cache results for short windows.
A flag that says "feature is enabled for all customers" forever. That's not a flag; it's the feature being live. Remove the flag.
Hundreds of flags; no one knows what they all do. Audit periodically.
Code that's if (flag1) ... if (flag2) ... if (flag3) .... Refactor to clean abstractions.
Production code depends on flag service being up. If it's down, what happens? Either fall back to a default or cache the last known values.