Use Cases

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-feature

Flipswitch:

  1. Hashes the user's targetingKey
  2. Maps the hash to a bucket (0-99)
  3. 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 />;
MutableContext context = new MutableContext(userId);
boolean showNewPricing = client.getBooleanValue("new-pricing-page", false, context);

return showNewPricing ? newPricingPage() : oldPricingPage();
context = EvaluationContext(targeting_key=user_id)
show_new_pricing = client.get_boolean_value("new-pricing-page", False, context)

return new_pricing_page() if show_new_pricing else old_pricing_page()
evalCtx := openfeature.NewEvaluationContext(userID, nil)
showNewPricing, _ := client.BooleanValue(ctx, "new-pricing-page", false, evalCtx)

if showNewPricing {
    return newPricingPage()
}
return 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% -> true

Watch 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!
});
// Good: consistent per user
MutableContext context = new MutableContext(user.getId());  // User always sees same variant
client.getBooleanValue("feature", false, context);

// Good: consistent per session (for anonymous users)
MutableContext sessionContext = new MutableContext(sessionId);
client.getBooleanValue("feature", false, sessionContext);

// Bad: inconsistent
MutableContext badContext = new MutableContext(UUID.randomUUID().toString());  // Different every time!
client.getBooleanValue("feature", false, badContext);
# Good: consistent per user
context = EvaluationContext(targeting_key=user.id)  # User always sees same variant
client.get_boolean_value("feature", False, context)

# Good: consistent per session (for anonymous users)
session_context = EvaluationContext(targeting_key=session_id)
client.get_boolean_value("feature", False, session_context)

# Bad: inconsistent
import uuid
bad_context = EvaluationContext(targeting_key=str(uuid.uuid4()))  # Different every time!
client.get_boolean_value("feature", False, bad_context)
// Good: consistent per user
evalCtx := openfeature.NewEvaluationContext(user.ID, nil)  // User always sees same variant
client.BooleanValue(ctx, "feature", false, evalCtx)

// Good: consistent per session (for anonymous users)
sessionCtx := openfeature.NewEvaluationContext(sessionID, nil)
client.BooleanValue(ctx, "feature", false, sessionCtx)

// Bad: inconsistent
badCtx := openfeature.NewEvaluationContext(uuid.New().String(), nil)  // Different every time!
client.BooleanValue(ctx, "feature", false, badCtx)

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']
});
// Server-side with request correlation
MutableContext context = new MutableContext(request.getHeader("x-request-id"));
client.getBooleanValue("feature", false, context);
# Server-side with request correlation
context = EvaluationContext(targeting_key=request.headers.get("x-request-id"))
client.get_boolean_value("feature", False, context)
// Server-side with request correlation
evalCtx := openfeature.NewEvaluationContext(r.Header.Get("X-Request-ID"), nil)
client.BooleanValue(ctx, "feature", false, evalCtx)

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% -> disabled

Internal 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-b
const variant = await client.getStringValue('checkout-experiment', 'control', {
  targetingKey: userId
});
MutableContext context = new MutableContext(userId);
String variant = client.getStringValue("checkout-experiment", "control", context);
context = EvaluationContext(targeting_key=user_id)
variant = client.get_string_value("checkout-experiment", "control", context)
evalCtx := openfeature.NewEvaluationContext(userID, nil)
variant, _ := client.StringValue(ctx, "checkout-experiment", "control", evalCtx)

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 production

This 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-feature

All users immediately get the old behavior. Users who had the new feature switch to the old one.

Cleanup

At 100% with stable metrics:

  1. Remove the flag check from code
  2. Keep the flag for a week (in case you need to roll back)
  3. Delete the flag
  4. Remove the old code path
// Before
if (showNewPricing) {
  return <NewPricingPage />;
} else {
  return <OldPricingPage />;
}

// After cleanup
return <NewPricingPage />;
// Before
if (showNewPricing) {
    return newPricingPage();
} else {
    return oldPricingPage();
}

// After cleanup
return newPricingPage();
# Before
if show_new_pricing:
    return new_pricing_page()
else:
    return old_pricing_page()

# After cleanup
return new_pricing_page()
// Before
if showNewPricing {
    return newPricingPage()
} else {
    return oldPricingPage()
}

// After cleanup
return newPricingPage()

On this page