Feature Toggle Management
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.
Flag types
Different uses; different lifecycles.
Release flags
Wrap in-progress features. Enable for testing, gradual rollout. Remove after the feature is stable.
Lifetime: weeks to months.
Experiment flags (A/B test)
Compare variants for product experiments. Statistical analysis decides winner.
Lifetime: weeks for the experiment.
Permission flags
Premium features, customer-specific access. Persistent state per customer.
Lifetime: indefinite (driven by business).
Operational flags / kill switches
Quickly disable a problematic feature. Circuit breakers.
Lifetime: indefinite (kept for emergencies).
These are different tools. Treating them all the same causes problems.
Implementation
Boolean flag
```javascript
if (featureFlag.isEnabled('new-checkout')) {
showNewCheckout();
} else {
showOldCheckout();
}
```
Simple, common.
Multivariate flag
```javascript
const variant = featureFlag.variant('checkout-experiment');
if (variant === 'A') ...
else if (variant === 'B') ...
```
For experiments with multiple options.
Targeting
Flags evaluated based on user, group, percentage:
```javascript
if (featureFlag.isEnabled('new-feature', { userId, groups: user.groups })) {...}
```
The flag service evaluates rules: this user, this group, this percentage.
Tools
Hosted services
- **LaunchDarkly**: market leader; full-featured
- **Split**: comparable
- **Statsig**: experimentation focus
- **PostHog**: open-source; product analytics + flags
- **Flagsmith**: open-source; self-hostable
Self-built
For small teams, a database table + simple service can suffice. The "build vs. buy" depends on scale and feature needs.
Open-source self-hosted
Unleash, GrowthBook, OpenFeature standardization.
The lifecycle
Each flag should have:
1. **Creation**: with intent, owner, expected lifetime
2. **Active use**: while the feature is gated
3. **Cleanup**: remove the flag from code; the feature is permanent
The cleanup step is where most teams fail. Flags accumulate; old code paths persist; the codebase gets crufty.
Retirement discipline
Practices that prevent flag rot:
Owner per flag
Every flag has an owner. The owner is responsible for retirement.
Expected lifetime
Set when created. "Remove by end of Q3 2026." Not a hard deadline but a reminder.
Periodic flag audits
Quarterly: list all flags. Which are old? Owner explains why or removes.
Cleanup PRs
When retiring a flag, the PR removes both the flag check and the deprecated code path. The "if/else" becomes one path.
Tools that warn
Some flag services flag (heh) old flags as candidates for removal.
What flag is for vs. not
Flag is good for
- Releasing features behind a kill switch
- Gradual rollout (1% → 10% → 50% → 100%)
- A/B experiments
- Customer-specific features
- Emergency kill switches
Flag is misused for
- Long-term configuration (use config, not flags)
- Permanent feature differentiation (use proper architecture)
- Avoiding decisions ("we'll figure it out later")
- Hiding bad code ("we'll just keep the old path forever")
Common patterns
Gradual rollout
Start with internal users, then 1%, then 10%, then 100%. Watch metrics at each step.
Kill switch
For risky features, the kill switch is a flag that disables in <1 minute. Used in incidents.
Sticky bucketing
For experiments, ensure a user always sees the same variant. Random per-call assignment ruins experiment quality.
Override per environment
Different defaults in dev, staging, production.
Targeting attribute caching
Flag evaluation can be expensive (network call). Cache results for short windows.
Anti-patterns
Flags that become "configuration"
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.
Flag explosion
Hundreds of flags; no one knows what they all do. Audit periodically.
Flag-driven branching that re-creates if/else hell
Code that's `if (flag1) ... if (flag2) ... if (flag3) ...`. Refactor to clean abstractions.
Flag service as single point of failure
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.
Common failure patterns
- **Flags never cleaned up.** Permanent debt.
- **Flag service outage = production outage.** Cache fall-back.
- **Targeting rules accumulate.** "Everyone except customer X except their team Y except..."
- **No flag visibility.** Engineers don't know which flags are active.
- **Flag for wrong purpose.** Configuration as flag; never retired.
Further Reading
- [TrunkBasedDevelopment](TrunkBasedDevelopment) — Flags enable trunk-based
- [CiCdPipelines](CiCdPipelines) — CI/CD with flags
- [ReleaseEngineering](ReleaseEngineering) — Adjacent practice
- [DevOpsAndSre Hub](DevOpsAndSreHub) — Cluster index