Why doesn't my rollout hit the percentage I set?

Last updated:

Because bucketing assigns each user a fixed number from 0 to 99 and counts how many land under your threshold. It does not meter traffic. With a few hundred users the observed share can sit several points either side of the configured one, and that is expected. Persistent skew usually means anonymous contexts, which the hosted endpoint spreads randomly on every call instead of bucketing.

You set a flag to 20% and your dashboard shows 26%. Nothing is broken. A percentage rollout is not a traffic meter, and it never counts requests.

Each evaluation hashes a per-flag salt together with the user’s key, takes the result modulo 100, and gets a number from 0 to 99. Variations are laid out in order with their weights accumulated, and the first band the number falls into wins. So a 20% rollout means “every user whose bucket is 0 through 19”, not “one request in five”.

Why the observed share drifts

The assignment is per user and permanent, so your observed percentage is a sample of however many distinct users happened to show up.

At a few hundred users, landing several points off the configured number is ordinary sampling noise. At tens of thousands it tightens considerably. Activity is uneven too, so one heavy user in the enabled bucket can drag a request-weighted figure around while the user-weighted figure sits exactly where it should. Count distinct users when you check.

When it is not noise

Two causes produce skew that does not settle down as traffic grows.

The first is anonymous contexts. When no user key is present, the two evaluation paths deliberately diverge:

// Keyed: stable bucket, same variation every time
client.boolVariation('new-checkout', { userId: 'u_8134' }, false);
// Keyless against the hosted endpoint: fresh random bucket per call
client.boolVariation('new-checkout', {}, false);

Against the hosted evaluation endpoint a keyless context gets a fresh random bucket on every call, which spreads anonymous traffic across variations by weight but gives nobody a stable experience. A server-side SDK evaluating locally does the opposite and serves the first variation every time. Either way your percentages stop describing people.

The second is a rule sitting above the rollout. Rules run in priority order and the first match wins, so users captured by a fixed-serve rule never reach the fallthrough at all. If half your traffic matches an internal-staff rule, the rollout only ever divides the remaining half.

The fix

Pass a stable identifier on every evaluation. If your users are genuinely anonymous, generate a key once, persist it in a cookie or local storage, and send it as the user_id. That single change turns a random spread into real buckets.

Then check what sits above the rollout in your rule list before trusting the number.

Bucketing mechanics are covered in Rollout strategies, and rule ordering in Targeting and segments.

Still stuck?

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