Do I need a user ID to evaluate a feature flag?
Last updated:
Not for on/off flags or targeting rules that match on other attributes. You need one for percentage rollouts, because the bucket comes from hashing the identifier with the flag key. Without it a server SDK evaluating locally serves the first (control) variation, while the hosted evaluation endpoint spreads anonymous traffic by weight with no per-user stickiness.
A flag that is simply on or off needs no context at all, and neither does a targeting rule keyed on something else, like a plan tier or a region. The identifier matters for one thing: deciding which side of a percentage split a given caller lands on.
Why the identifier is what makes a rollout stick
Bucketing hashes the user identifier together with the flag key, and the result is a number from 0 to 100. Same inputs, same number, every time. That is what stops a user flipping between variations on refresh, across devices, or between API calls.
Take the identifier away and there is nothing stable left to hash.
What actually happens without one
The two evaluation paths diverge here, and the difference catches people out.
A server SDK evaluating locally serves the first variation, the control. It does not randomize. An anonymous caller gets the safe side of the split deterministically, which is the conservative choice for a backend that may be handling a logged-out request.
The hosted evaluation endpoint spreads anonymous traffic across variations by weight. The overall split comes out roughly right, but no individual caller is sticky, so the same visitor can see different variations on consecutive requests.
Neither is a bug. They are different answers to the question of what to do when stickiness is impossible, chosen to suit where each one runs.
What to do for logged-out users
Generate a stable key yourself and pass it as the identifier. A UUID in a cookie or in local storage is enough:
let id = localStorage.getItem('ff-anon-id');if (!id) { id = crypto.randomUUID(); localStorage.setItem('ff-anon-id', id);}
const client = FeatureflipClient.get({ clientKey: 'sdk_client_a1b2c3d4', context: { user_id: id },});Now the anonymous visitor buckets like any other user and stays put. Swap the generated key for the real user ID once they sign in, and be aware that the bucket may change at that moment, because the hash input changed. For a checkout experiment that is usually fine. For anything where crossing the boundary mid-session would be jarring, hold the anonymous key until the session ends.
How much anonymity you keep is your call
Supplying a key buys stickiness back at any point, so the decision is about how much you want to track rather than about a limit in the flag system.
Bucketing mechanics, including the anonymous case, are documented in Rollout strategies.
Related questions
Why do two users get different variations?
That is what a percentage rollout does. Bucketing hashes the user ID with the flag key, so the split is per user and per flag, and never lines up across flags.
Why is my feature flag returning the default value?
Six reasons a Featureflip flag falls back to the default you passed, ordered by how often each one is the real cause, with the check for each.
How do I test code that uses feature flags?
Every Featureflip SDK ships a test client you construct with fixed flag values. No network, no initialization, no mocking the SDK surface yourself.
Still stuck?
The docs cover every SDK, and the free Solo plan is enough to reproduce most of these locally.