Implement a reusable feature toggle system with multiple conditions
A flag evaluator: flags defined with rules (boolean, user/role targeting, percentage rollout, environment, date windows), evaluated against a context (user, env). Expose via a provider + useFlag hook. Cache the evaluated flags, support remote config, and default safely when evaluation fails.
A feature toggle system is a rules engine: given a flag's conditions and a context, decide on or off — wrapped in an ergonomic API.
The flag model
A flag isn't just a boolean — it has rules:
{
key: "new-checkout",
enabled: true, // master switch
rules: [
{ type: "user", userIds: ["u1", "u2"] }, // explicit targeting
{ type: "role", roles: ["beta-tester"] },
{ type: "percentage", rollout: 25 }, // gradual rollout
{ type: "environment", envs: ["staging"] },
{ type: "dateWindow", start: "...", end: "..." },
],
}The evaluator
A pure function: (flag, context) => boolean, where context is { userId, role, env, ... }.
function evaluate(flag, context) {
if (!flag.enabled) return false;
// a flag is ON if the context matches its rules (any/all per design)
return flag.rules.some((rule) => matchesRule(rule, context));
}- Percentage rollout must be stable per user — hash
userId + flagKeyto a number 0–99 and compare torollout. Same user always gets the same answer; otherwise the UI flickers on every load. - Decide any vs all rule semantics (OR vs AND), or support both.
The React API
<FeatureFlagProvider flags={flags} context={{ userId, role, env }}>
...
</FeatureFlagProvider>
function Checkout() {
const isNew = useFlag("new-checkout");
return isNew ? <NewCheckout /> : <OldCheckout />;
}A provider holds the flags + context and the evaluated results; useFlag(key) reads the evaluated value.
Production concerns
- Remote config — flags fetched from a service (LaunchDarkly, or your own), so you flip features without a deploy. Refresh/poll or stream updates.
- Caching & performance — evaluate once per flag per context, memoize; don't re-evaluate every render.
- Safe defaults — if the flag service is unreachable or a flag is unknown, fall back to a safe default (usually "off") — never crash because flags failed to load.
- SSR consistency — evaluate the same way on server and client to avoid hydration mismatch.
- Cleanup discipline — flags are tech debt; track and remove stale ones.
- Analytics/exposure logging — record which users saw which variant (for A/B analysis).
- Kill switch — every risky flag can be turned off instantly.
The framing
"It's a rules engine. A flag has a master switch plus rules — explicit user/role targeting, percentage rollout, environment, date windows. A pure evaluator (flag, context) => boolean checks the context against the rules; the key subtlety is percentage rollout must hash userId + flagKey so it's stable per user, not random per load. I'd expose it as a provider holding flags and context, with a useFlag(key) hook reading memoized evaluations. Production-wise: remote config so I can flip flags without a deploy, safe 'off' defaults if the service is down, SSR-consistent evaluation, exposure logging for A/B, and discipline around removing stale flags."
Follow-up questions
- •Why must percentage rollout be stable per user?
- •What should happen if the flag service is unreachable?
- •How do remote flags let you change features without deploying?
- •How do you avoid feature flags becoming permanent tech debt?
Common mistakes
- •Random percentage rollout that flickers per page load.
- •No safe default when flag evaluation fails — crashing instead.
- •Re-evaluating flags on every render instead of memoizing.
- •Inconsistent server/client evaluation causing hydration mismatch.
- •Never removing stale flags — accumulating tech debt.
Performance considerations
- •Evaluate once per flag+context and memoize; don't block first render on a remote fetch — render with cached/default values and update when flags arrive. Streaming/polling updates should be debounced.
Edge cases
- •Flag service down or slow on load.
- •Unknown flag key requested.
- •User with no id (anonymous) for percentage rollout.
- •Conflicting rules within one flag.
- •Flag changes mid-session.
Real-world examples
- •LaunchDarkly, Split, Unleash — exactly this rules-engine + SDK model.
- •Gradual rollouts: shipping a feature to 5% → 25% → 100% via a percentage rule.