Feature Flags Cheat Sheet
Patterns for using feature flags to decouple deployment from release, run experiments, and control rollout risk.
Types of Feature Flags
Common categorizations of flags by purpose and lifespan.
- Release flags- Short-lived, gate an incomplete feature until it's ready; removed after full rollout
- Experiment flags (A/B)- Route users into variants to measure impact on a metric
- Ops flags- Kill switches to disable a feature or subsystem under load or failure
- Permission flags- Long-lived, gate features by plan tier or entitlement
Basic Flag Evaluation
Typical client-side pattern for checking a flag with a fallback default.
import { useFlag } from 'feature-flag-sdk';function Checkout() { const newFlowEnabled = useFlag('new-checkout-flow', false); // default: off if (newFlowEnabled) { return <NewCheckoutFlow />; } return <LegacyCheckoutFlow />;}
Unleash Feature Toggle Config
Example strategy config for a gradual percentage rollout.
{ "name": "new-checkout-flow", "enabled": true, "strategies": [ { "name": "gradualRolloutUserId", "parameters": { "percentage": "25", "groupId": "new-checkout-flow" } } ]}
Rollout Strategies
Ways to progressively expose a flagged feature.
- Percentage rollout- Gradually increase the share of traffic/users receiving the new behavior
- Ring/cohort deployment- Enable for internal users, then beta users, then everyone
- Targeted attributes- Enable by user segment, region, or account tier
- Kill switch- Instant global disable without a redeploy when something goes wrong
LaunchDarkly Server-Side Targeting by Context
Evaluate a flag server-side with a multi-context (user + org) payload for account-tier targeting.
const { init } = require('@launchdarkly/node-server-sdk');const client = init(process.env.LD_SDK_KEY);await client.waitForInitialization();const context = { kind: 'multi', user: { key: user.id, email: user.email }, organization: { key: org.id, plan: org.plan, name: org.name },};const showNewFlow = await client.variation( 'new-checkout-flow', context, false // fallback if flag/service unreachable);if (showNewFlow) { return renderNewCheckout(req, res);}return renderLegacyCheckout(req, res);
Flag Debt & Risk Signals
Signs a flag has overstayed its purpose and needs cleanup.
- Stale release flag- At 100% rollout for weeks with no plan to remove it and delete the old code path
- Nested flag conditionals- Multiple flags gating the same function create combinatorial states that are never all tested
- Flag without an owner- No team accountable for evaluating and eventually deleting it
- Flag-dependent tests only- Test suite only exercises the default variation, leaving the other branch unverified in CI
- Config drift across environments- Flag state differs between staging and prod in ways nobody tracks, causing "works in staging" surprises
- Long-lived permission flags treated as release flags- Permanent entitlement logic mixed into the same system as short-lived rollout flags, with no lifecycle distinction
Streaming Flag Updates (SSE) Without Polling
Subscribe to a flag-update stream so clients pick up changes within milliseconds instead of polling.
const es = new EventSource(`${FLAGS_STREAM_URL}?client=${clientId}`, { headers: { Authorization: `Bearer ${sdkKey}` },});es.addEventListener('put', (msg) => { const flags = JSON.parse(msg.data); flagStore.replaceAll(flags);});es.addEventListener('patch', (msg) => { const { key, value } = JSON.parse(msg.data); flagStore.set(key, value); // single flag changed, avoid full reload});es.onerror = () => { // SDK falls back to last known good local cache; never block rendering on stream health metrics.increment('flags.stream.disconnect');};
Testing Strategies for Flagged Code
Ways to keep both sides of a flag verified instead of only the default path.
- Matrix CI runs- Run the test suite twice per PR, once with the flag forced on and once forced off
- Contract tests on the variation function- Assert the fallback value type matches the expected variant type, catching SDK misconfiguration
- Shadow/dark launch- Execute the new code path in production without serving its output, comparing results against the old path
- Flag override in E2E tests- Force specific flag states via test-only override headers/cookies rather than relying on real evaluation
- Chaos-testing the flag service itself- Verify the app behaves correctly (falls back safely) when the flag provider is unreachable
OpenFeature Provider Abstraction
Use the vendor-neutral OpenFeature API so the underlying flag provider can be swapped without touching call sites.
const { OpenFeature } = require('@openfeature/server-sdk');const { LaunchDarklyProvider } = require('@openfeature/launchdarkly-server-provider');OpenFeature.setProvider(new LaunchDarklyProvider(ldClient));const client = OpenFeature.getClient();const enabled = await client.getBooleanValue( 'new-checkout-flow', false, { targetingKey: user.id, plan: org.plan });// Swapping providers later (e.g. to Unleash) requires no change below this line
Treat flags as tech debt with an expiration date — track stale release flags and delete them once a feature is fully rolled out, or the codebase accumulates unreadable nested conditionals.