Skip to content

Percentage Rollouts

Percentage rollouts let you release features to a subset of users and gradually increase the rollout as you gain confidence.

How It Works

  1. Create a flag with a percentage (e.g., 25%)
  2. Use isEnabledForUser() with a user identifier
  3. The SDK uses consistent hashing to determine if the user is in the rollout
  4. The same user always gets the same result for the same flag

Basic Usage

import { Featsync } from '@featsync/sdk';
const featsync = new Featsync({
apiKey: 'fs_your_api_key',
});
// Get the current user's ID
const userId = getCurrentUserId();
// Check if this user is in the rollout
if (await featsync.isEnabledForUser('new-checkout', userId)) {
showNewCheckout(); // ~25% of users see this
} else {
showOldCheckout(); // ~75% of users see this
}

Consistent Hashing

The SDK uses consistent hashing to ensure:

  • Same user, same result - User “alice” will always get the same value for flag “new-checkout”
  • Different flags, different buckets - User “alice” might be in rollout for “new-checkout” but not for “new-pricing”
  • No flip-flopping - Users don’t see features appear and disappear randomly
// Alice will always get the same result for this flag
const aliceResult = await featsync.isEnabledForUser('new-checkout', 'alice');
// Bob might get a different result
const bobResult = await featsync.isEnabledForUser('new-checkout', 'bob');
// Alice's result for a different flag is independent
const aliceOther = await featsync.isEnabledForUser('new-pricing', 'alice');

User Identifiers

The user identifier should be:

  • Unique - Different users should have different IDs
  • Stable - The same user should always have the same ID
  • Non-PII - Don’t use email addresses directly

Good Examples

// Database ID
await featsync.isEnabledForUser('feature', 'user_12345');
// UUID
await featsync.isEnabledForUser('feature', 'a1b2c3d4-e5f6-...');
// Hashed email
import { createHash } from 'crypto';
const hashedEmail = createHash('sha256').update(email).digest('hex');
await featsync.isEnabledForUser('feature', hashedEmail);

Bad Examples

// Don't use raw emails
await featsync.isEnabledForUser('feature', 'alice@example.com'); // Bad!
// Don't use session IDs (not stable)
await featsync.isEnabledForUser('feature', sessionId); // Bad!

Rollout Workflow

Here’s a typical workflow for rolling out a feature:

1. Start Small (10%)

// In the dashboard, set percentage to 10%
// ~10% of users will see the feature
if (await featsync.isEnabledForUser('new-feature', userId)) {
showNewFeature();
}

2. Monitor Metrics

Watch your error rates, user feedback, and key metrics. If everything looks good, continue.

3. Increase Gradually

In the dashboard, increase the percentage:

  • 10% → 25%
  • 25% → 50%
  • 50% → 100%

4. Full Rollout

Once at 100%, you can:

  1. Keep the flag for a quick kill switch
  2. Or remove the flag check from your code

Anonymous Users

For users without accounts, you can use any stable identifier:

// Use a cookie-based ID
const anonymousId = getOrCreateAnonymousId();
await featsync.isEnabledForUser('feature', anonymousId);
anonymous-id.js
function getOrCreateAnonymousId() {
let id = localStorage.getItem('anonymous_id');
if (!id) {
id = crypto.randomUUID();
localStorage.setItem('anonymous_id', id);
}
return id;
}

Boolean Flags with isEnabledForUser

You can use isEnabledForUser() with boolean flags too. It behaves the same as isEnabled():

// For a boolean flag (100% or 0%)
// Both return the same result:
await featsync.isEnabled('my-flag');
await featsync.isEnabledForUser('my-flag', userId);

Getting Percentage Info

To see the rollout percentage in your code:

const flag = await featsync.getFlag('gradual-feature');
if (flag.percentage !== null) {
console.log(`Rollout at ${flag.percentage}%`);
}

Testing Percentage Rollouts

To test your rollout logic, try different user IDs:

// See which test users are in the rollout
for (const testUserId of ['user_1', 'user_2', 'user_3', 'user_4', 'user_5']) {
const enabled = await featsync.isEnabledForUser('gradual-feature', testUserId);
console.log(`${testUserId}: ${enabled ? 'IN' : 'OUT'}`);
}

Next Steps