Feature Flags in Practice
Introduction¶
A feature flag is a runtime switch that changes behaviour without a deploy. It's what makes trunk-based development and progressive delivery work: merge incomplete code dark, turn it on when ready, turn it off instantly if it breaks.
Four types (they have different lifecycles)¶
- Release flags — hide in-progress work; on when the feature ships. Short-lived — remove within weeks of full rollout.
- Ops / kill switches — turn off an expensive or fragile feature under load or during an incident. Long-lived by design.
- Experiment flags — A/B tests; assign users to variants, measure. Removed when the experiment concludes.
- Permission / entitlement flags — gate features by plan, role, or customer. Permanent — really configuration, not a "flag" in the temporary sense.
Mixing lifecycles is where flag debt comes from — track the type and an expiry.
Where to evaluate¶
- Server-side — the flag SDK evaluates in your backend; the client never sees flags for features it can't access. Preferred for anything security- or cost-sensitive.
- Client-side — the browser/app fetches flag values. Fine for UI toggles; never gate access to data this way (the user can flip it).
- Edge / config — evaluated at the CDN or in a config file for infrastructure-level flags.
Targeting and gradual rollout¶
flag: new-checkout
default: off
rules:
- if user.plan == "enterprise" then on
- if user.id in [beta-testers] then on
- rollout: 5% of users (sticky by user.id hash) # 5 -> 25 -> 50 -> 100
Sticky assignment (hash the user ID) matters: a user shouldn't flip between old and new on every request. The same hash also lets experiment flags keep a user in one variant.
Kill switches¶
Every risky feature gets an ops flag. During an incident:
flagctl set expensive-recommendations off --reason "INC-1234, CPU saturation"
No deploy, effective in seconds. Wire it so the fallback path is tested — a kill switch that leads to an untested code path is a second outage.
Flag debt and cleanup¶
Flags accumulate. A stale release flag is:
- a branch in the code nobody exercises (rot),
- a config-explosion risk (2^N combinations),
- confusing during incidents ("is this flag on? what does it do?").
Practices:
- Name flags with an owner and a created-date (
checkout.new_flow.2026q3). - Track expiry — the flag system or a linter flags overdue ones.
- A "clean up flag X" ticket created when the flag is created.
- Percentage-100 for N weeks → remove the flag and the old code path as a routine chore.
- Some teams run a scheduled job that lists flags older than 90 days for review.
Testing code behind flags¶
- Test both paths — old and new — until the old one is removed. Parametrise tests over the flag value.
- In CI, run the suite with the flag both on and off (or at least the affected tests).
- Integration/E2E: a way to force a flag value per test (a header, a test-only override).
- Don't let the flag default drift — the default should be the safe/current behaviour until rollout completes, then flip the default, then remove.
Build vs buy¶
- Buy (LaunchDarkly, Flagsmith, Unleash-hosted, Statsig) — targeting UI, audit log, SDKs, streaming updates, experimentation stats. Worth it past a handful of flags.
- Open source self-host — Unleash, Flagsmith, Flipt, GrowthBook.
- DIY — a config file / DB table + a small SDK. Fine for < ~10 simple flags; you'll outgrow it (targeting, audit, gradual rollout, real-time updates).
Verification and troubleshooting¶
- A user reports the feature is on/off inconsistently — non-sticky rollout (hash the user, not the request), or client-side + server-side flags disagreeing, or SDK cache staleness. Check the evaluation logs (good flag systems log every evaluation with the reason).
- Flag flip had no effect — SDK not receiving updates (streaming connection down, falling back to a stale poll), the code reads the flag once at startup instead of per request, or you flipped it in the wrong environment.
- Kill switch didn't help — the fallback path was broken/untested, or the flag only gates the new code and the shared code is the problem.
- Combinatorial bugs — two flags interact. Reduce concurrent in-flight flags; remove flags promptly.
- Flag config is a single point of failure — the SDK must fail safe to a bundled default if the flag service is unreachable; verify that behaviour.
- Experiment results look wrong — assignment not sticky, sample ratio mismatch (SRM — one variant getting more traffic than configured), or peeking at results before the test concludes.
Related tools and reading¶
- On-site: (no dedicated tool) — see Regex Tester for targeting rules.
- Related posts: Trunk-based development, Blue-green vs canary deployments.
Stuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.More in DevOps & CI/CD
OpenTelemetry for Distributed Tracing
A trace follows one request across services. OpenTelemetry is the vendor-neutral way to produce them — here's the model, the Collector, and sampling that won't bankrupt you.
October 24, 2026pre-commit Hooks That Actually Help
pre-commit runs checks before a commit lands, catching formatting, secrets, and syntax errors locally. Here's a config that helps without being annoying.
October 24, 2026Chaos Engineering Basics
Chaos engineering is controlled experiments that inject failure to verify your system tolerates it. Here's the method, safe blast radius, starter experiments, and game days.
October 24, 2026