Gradual Rollouts
Gradually release features from 0% to 100%
A gradual rollout releases a feature to a growing portion of users over time. Start at 0%, increase gradually, reach 100%.
How It Works
When you set a gradual rollout:
10% -> new-feature
90% -> old-featureFlipswitch:
- Hashes the user's
targetingKey - Maps the hash to a bucket (0-99)
- Returns the variant based on bucket
User alice@example.com might hash to bucket 42. With 10% rollout, she gets the old feature. Increase to 50%, and she gets the new feature.
Properties
Deterministic. Same user, same bucket, same variant. Alice always sees the same thing.
Consistent. Increasing from 10% to 20% doesn't reassign users in the first 10%. Users who had the new feature keep it.
Even distribution. Hash function distributes users evenly across buckets. 10% means roughly 10% of users, not exactly.
Implementation
1. Create the Flag
Key: new-pricing-page
Type: Boolean
Default: false (old page)2. Configure Rollout
Start at 0%:
0% -> true (new page)
100% -> false (old page)3. Evaluate in Code
const showNewPricing = await client.getBooleanValue('new-pricing-page', false, {
targetingKey: userId
});
return showNewPricing ? <NewPricingPage /> : <OldPricingPage />;4. Increase Gradually
Day 1: 1% -> true
Day 3: 5% -> true
Day 5: 10% -> true
Day 7: 25% -> true
Day 10: 50% -> true
Day 14: 100% -> trueWatch metrics at each stage before increasing.
The targetingKey
The targeting key determines which bucket a user falls into:
// Good: consistent per user
await client.getBooleanValue('feature', false, {
targetingKey: user.id // User always sees same variant
});
// Good: consistent per session (for anonymous users)
await client.getBooleanValue('feature', false, {
targetingKey: sessionId
});
// Bad: inconsistent
await client.getBooleanValue('feature', false, {
targetingKey: Math.random().toString() // Different every time!
});For stateless services, use a consistent identifier like request ID:
// Server-side with request correlation
await client.getBooleanValue('feature', false, {
targetingKey: request.headers['x-request-id']
});Combining with Rules
Targeting rules are evaluated before gradual rollouts. If a rule matches, the rollout is skipped:
Targeting Rules:
Rule 1: IF user in segment "internal" THEN return "enabled"
Rule 2: IF user in segment "beta" THEN return "enabled"
Gradual Rollout (for everyone else):
10% -> enabled
90% -> disabledInternal and beta users always get the new feature (via rules). Everyone else has a 10% chance (via rollout).
Multi-Variant Rollouts
For A/B tests or multiple variants:
Type: String
Variants: control, treatment-a, treatment-b
Rollout:
40% -> control
30% -> treatment-a
30% -> treatment-bconst variant = await client.getStringValue('checkout-experiment', 'control', {
targetingKey: userId
});Ring-Based Rollouts
Some teams use "rings" for staged rollouts:
Ring 0: Internal employees (segment rule)
Ring 1: 1% of production
Ring 2: 10% of production
Ring 3: 50% of production
Ring 4: 100% of productionThis combines segments (for ring 0) with gradual rollouts (for rings 1-4).
Rollback
If something goes wrong at 25%:
Before: 25% -> new-feature
After: 0% -> new-featureAll users immediately get the old behavior. Users who had the new feature switch to the old one.
Cleanup
At 100% with stable metrics:
- Remove the flag check from code
- Keep the flag for a week (in case you need to roll back)
- Delete the flag
- Remove the old code path
// Before
if (showNewPricing) {
return <NewPricingPage />;
} else {
return <OldPricingPage />;
}
// After cleanup
return <NewPricingPage />;