Why do two SDKs return different variations?

Last updated:

Almost always because the evaluation context is keyless in one of them. Bucketing needs a stable user key, and the two evaluation paths handle its absence differently on purpose: a server SDK evaluating locally serves the first variation, while the hosted endpoint assigns a fresh random bucket per call. Pass the same user key everywhere and the two agree, because every SDK runs the same hash.

Your Go service says true and your Node service says false for the same user on the same flag. Before suspecting either SDK, check what you are passing as the user key, because that is the cause in most reports of this.

Every SDK computes the same bucket from the same inputs. The hash takes a per-flag salt and the user’s key, and the result is a number from 0 to 99 that decides which weighted band the user falls into. Same salt and same key produce the same number in every language we ship. That parity is covered by a shared set of golden vectors run against each SDK, so drift between implementations gets caught in CI rather than in your logs.

What actually differs

The divergence lives in what happens when the key is missing, and it is deliberate.

// Both services pass the key: identical result everywhere
boolVariation('new-pricing', { userId: 'u_8134' }, false);
// One service omits it: results diverge by evaluation path
boolVariation('new-pricing', {}, false);

A server-side SDK evaluating locally serves the first variation, deterministically, every time. The hosted evaluation endpoint instead assigns a random bucket per call so anonymous traffic spreads across variations by weight. Neither is wrong, but they answer different questions, and a service that quietly drops the user key will disagree with one that does not.

Attribute naming is the second thing to check. The built-in identifier is accepted as either userId or user_id and the two are treated as the same field, but a custom attribute is matched literally. A context sending accountId where your rule expects account_id quietly fails the condition and raises nothing.

Rule out the config first

The two services must also be reading the same configuration. Different SDK keys mean different environments, and a flag at 50% in staging and 10% in production will disagree for perfectly good reasons. Confirm both keys belong to the environment you think they do before debugging the evaluation itself.

The fix

Send the same stable key from every service that evaluates the flag, and use the same attribute names in each. Where a request genuinely has no user, decide deliberately whether you want the anonymous traffic spread or the control variation, because the path you choose determines which you get.

The keyed and keyless behaviours are set out in Rollout strategies, and per-environment configuration in Environments.

Still stuck?

The docs cover every SDK, and the free Solo plan is enough to reproduce most of these locally.