All questions

Feature Flag Client

Premium

Feature Flag Client

A feature flag client decides whether a feature is turned on for a given user, combining a master on/off switch, targeting rules that force specific users on or off, and a percentage rollout that ramps a feature to a growing slice of the audience. Teams ship code behind flags so they can release to 1% of users, watch for errors, then widen to 100% with no redeploy. The catch is that a rollout has to be sticky: a user in the enabled 10% today must stay in it tomorrow, or you can never measure the effect of a change. You will build the evaluator at the center of this — a factory that reads a flag config and answers two questions for any user.

Signature

type Flag = {
  enabled: boolean;                 // master switch — false means off for everyone
  rollout?: number;                 // 0..100; the share of users the flag is on for
  rules?: Array<{                   // targeting overrides, checked in order
    attribute: string;
    value: unknown;
    enabled: boolean;
  }>;
  variants?: Array<{ key: string; weight: number }>; // A/B buckets
};

type Context = { userId: string; [attribute: string]: unknown };

function featureFlagClient(flags: Record<string, Flag>): {
  isEnabled(key: string, context: Context): boolean;
  variant(key: string, context: Context): string | null;
};

Examples

A 50% rollout that stays sticky per user:

const client = featureFlagClient({
  'checkout-v2': {
    enabled: true,
    rollout: 50,
    rules: [{ attribute: 'plan', value: 'enterprise', enabled: true }],
    variants: [
      { key: 'control', weight: 60 },
      { key: 'treatment', weight: 30 },
      { key: 'holdout', weight: 10 },
    ],
  },
});

// u_530 falls in the enabled half (bucket 22 < 50) and stays there on every call.
client.isEnabled('checkout-v2', { userId: 'u_530' }); // true
client.isEnabled('checkout-v2', { userId: 'u_530' }); // true — same answer, always

// u_842 falls in the disabled half (bucket 80 >= 50).
client.isEnabled('checkout-v2', { userId: 'u_842' }); // false

A targeting rule overriding the rollout, and a sticky variant:

// u_842 is off by the rollout, but the enterprise rule forces it on —
// rules are checked before the rollout, and the first match wins.
client.isEnabled('checkout-v2', { userId: 'u_842', plan: 'enterprise' }); // true

// An enabled user draws a sticky variant; a user who is not enabled gets null.
client.variant('checkout-v2', { userId: 'u_530' }); // 'treatment'
client.variant('checkout-v2', { userId: 'u_842' }); // null — not in the experiment

Notes

  • Missing or disabled is false — if the flag key is absent or its enabled is false, isEnabled returns false, and no rule or rollout can turn it back on.
  • Rules win over the rollout — scan rules in order; the first rule whose context[attribute] equals its value returns that rule's enabled, ignoring the rollout entirely.
  • The rollout is sticky — derive a bucket from a hash of the key and userId, never from Math.random, so the same user always lands in the same bucket. A rollout of 100 is on for everyone, 0 for no one; a missing rollout means fully on.
  • Variants are sticky too — when the flag is enabled, pick a variant by hashing into the cumulative weight windows, and return null when the flag is not enabled for that user.
  • Do not worry about — where flags are stored, network fetching, caching, or config validation. You are handed the config object and only evaluate it.

FAQ

What is a percentage rollout for a feature flag?
A percentage rollout enables a flag for a fixed slice of users — a 25% rollout turns it on for one in four. You widen the slice over time to release gradually and catch problems before everyone is exposed.
Why must the rollout be deterministic instead of using Math.random?
Math.random re-rolls on every call, so the same user flips between on and off between page loads, which makes a gradual rollout or an A/B test impossible to measure. Hashing the user id gives each user one fixed bucket, so their assignment is sticky forever.
How do targeting rules interact with the rollout percentage?
Rules are checked first, in order, and the first one that matches wins outright — so you can force-enable a flag for enterprise customers or your own account regardless of the rollout. Only when no rule matches does the percentage bucket decide.

Unlock the solution & editor

  • Runnable editor + tests

    Solve in the browser with instant Jest feedback.

  • Detailed solutions

    Walkthroughs, edge cases, and complexity notes.

  • Multi-framework variants

    React, Vue, Vanilla, Angular — same question, different stacks.

Upgrade to Premium