How Percentage Rollouts Actually Work: Deterministic Bucketing Explained
You set a flag to 10% and a user refreshes the page five times. Do they see the new feature once out of ten refreshes?
No — and if your flag system says otherwise, you have a bug, not a rollout. A percentage rollout isn't a dice roll on every request. It's a stable assignment: each user lands in a bucket once, and stays there.
The Problem With Random
Naive implementations call Math.random()at evaluation time. That gets the aggregate percentage right and everything else wrong. Users flip between old and new UI on every page load. Your A/B test data is garbage because nobody had a consistent experience. Support gets "the button keeps disappearing" tickets. And storing assignments in a database to fix it adds a lookup to your hottest code path.
The standard solution needs no storage at all: hash your way to consistency.
Hash, Mod 100, Compare
Featureflow's SDKs compute a bucket by hashing three values together — a salt, the feature key, and the user's bucket key — then taking the result mod 100:
import { createHash } from 'crypto';
/** Stable bucket in [0, 100) for a user + feature pair. */
function bucket(salt: string, featureKey: string, userKey: string): number {
const hash = createHash('sha1')
.update(`${salt}:${featureKey}:${userKey}`)
.digest('hex');
return parseInt(hash.substring(0, 15), 16) % 100;
}
// A 10% rollout serves the new variant when bucket < 10
const inRollout = bucket(salt, 'new-checkout', user.key) < 10;Three properties fall out of this, and they're what make percentage rollouts usable in production:
- Sticky. Same inputs, same bucket — on every request, every server, every SDK. No session state, no assignment table.
- Expandable. Moving from 10% to 50% widens the threshold. Everyone below 10 is still below 50, so early users keep the feature — nobody gets it taken away mid-rollout.
- Independent per flag. Because the feature key is part of the hash, a user in the first 10% of one flag isn't automatically in the first 10% of every flag. Without that, the same slice of your user base would absorb the risk of every rollout you run.
Choose Your Bucket Key Deliberately
The one real decision left is what identifier goes into the hash. A user ID keeps the experience consistent across a person's devices. An anonymous session key is fine for logged-out traffic but re-buckets people when the session rotates. An organisation ID rolls out to whole teams at once — usually what you want in B2B, where two colleagues comparing screens should see the same thing.
Whatever you pick, keep it stable. A bucket key that changes on every visit degrades your careful deterministic rollout right back into Math.random().
This is exactly how Featureflow evaluates percentage rollouts across all of its server and client SDKs — the same user gets the same variant whether the flag is evaluated in your Java backend or the browser. See the docs for rollout and targeting details.
#FeatureFlags#PercentageRollouts#ProgressiveDelivery#ContinuousDelivery#DevOps
Roll out to 1%, 10%, 100% — deterministically
Start free with Featureflow and get sticky percentage rollouts out of the box.
Start Now (Free)Related Articles
Managing Feature Flags Across Environments Without Config Drift
Dev, staging, and prod each need different flag states — but keeping them consistent is where most teams stumble. Here's how to manage multi-environment flag config without drift.
Server-Side vs Client-Side Feature Flags: Choosing the Right Boundary
Same flag, same key — but move a decision from backend-only code to client-visible UI and you change its latency, exposure, and coordination risk. Here's how to use both safely.
Feature Flags vs Entitlements: When to Use Which (and When You Need Both)
Feature flags release code safely. Entitlements decide who's paid for what. Collapse them into one toggle system and you'll end up with billing tied to deploys, or risky releases hidden in your pricing layer.