---
title: "How to Do a Percentage Rollout with Feature Flags | Betterflag"
description: "A practical guide to percentage rollouts: sticky assignment, 10% then 50% then 100%, kill criteria, and how gradual rollouts differ from canary deploys."
canonical: https://betterflag.app/blog/percentage-rollouts
source: https://betterflag.app/blog/percentage-rollouts
---
# How to Do a Percentage Rollout with Feature Flags

**Published:** 2026-08-20  
**Author:** Mehdi  
**Tags:** feature-flags, rollouts, progressive-delivery, kill-switch, best-practices

A **percentage rollout** is how you stop betting the company on a Friday deploy. You ship the code to everyone, behind a feature flag, and you turn the feature on for 10% of users. If checkout holds, you go to 50%, then 100%. If it does not, you go to 0 without a revert.

That is [progressive delivery](/blog/what-are-feature-flags) in the small. No new build between steps. The flag percentage is the release.

## Sticky assignment, or your metrics are noise

The naive implementation is `Math.random() < 0.1`. That gives you 10% of *requests*, not 10% of users. A user will flip between old and new checkout as they navigate. Your funnel becomes unreadable. Support gets screenshots of both UIs from the same person.

Hash a stable id:

```ts
import { createHash } from "node:crypto";

function inRollout(userId: string, percent: number, flagKey: string): boolean {
  const digest = createHash("sha256")
    .update(`${flagKey}:${userId}`)
    .digest("hex");
  const bucket = parseInt(digest.slice(0, 8), 16) % 100;
  return bucket < percent;
}
```

Including the flag key in the hash means a user who was in the 10% for `checkout-v2` is not automatically in the 10% for `editor-v3`. Independent experiments stay independent.

You should not write this yourself in production. Your SDK already does it. The point is to know what "10%" means: the same 10% of user ids, every request, until you change the percentage.

Anonymous users need a cookie id set on first visit. Logins should alias that cookie to the account id so a user does not jump buckets when they sign in. IPs are a bad key.

## A ramp you can defend

There is no universal schedule. There is a schedule that matches your blast radius.

A default I like for user-facing product changes:

| Step | Share | How long | What you watch |
|---|---|---|---|
| Internal | staff / your user id | hours | Does it even load? |
| Beta | named cohort | a day | Support, obvious bugs |
| 10% | sticky percentage | hours to a day | Errors, checkout, latency |
| 50% | sticky percentage | hours to a day | Same, plus "did the 10% hold" |
| 100% | everyone | stay here, then delete the flag | Same |

Skip steps on a copy change. Do not skip steps on payments, auth, or a new query plan.

Ramp in the dashboard, the API, or from Claude Code over MCP. The code does not change:

```tsx
const showV2 = await flags.isEnabled("checkout-v2", { userId: user.id });
return showV2 ? <CheckoutV2 /> : <CheckoutV1 />;
```

`isEnabled` already knows the percentage. You are not passing `0.1` from the client. If you are, users can tamper with it.

## Kill criteria before you need them

A rollout without kill criteria is a delayed 100% ship. Write the line in the PR:

- Checkout conversion drops more than X% relative to the control window.
- 5xx on the new path exceeds Y.
- p95 latency on `/api/checkout` exceeds Z.
- Payment processor error rate doubles.

If a line is crossed, set the flag to off. That is a [kill switch](/blog/feature-flag-kill-switch). Debate the threshold on Monday, not during the incident.

The off path has to exist. If you deleted `<CheckoutV1 />` because "we will only ever go forward," you cannot roll back with a flag. You are waiting on a deploy again.

## Percentage rollouts vs canary deploys vs A/B tests

**Canary deploy:** a new *build* on a subset of servers. Catches "this binary segfaults" and "this migration locks the table." Does not catch "the new checkout confuses 8% of users." You still want canaries for infrastructure.

**Percentage rollout:** one build, subset of *users*. Catches product and application bugs. This is the feature flag job.

**A/B test:** a percentage split plus a stats engine. Use it when you need to know whether B is *better*, not just whether B is *safe*. If you do not have the traffic or the patience for significance, you wanted a rollout, not an experiment. Serious experimentation belongs in Statsig or GrowthBook, not a flags-only product.

Betterflag does rollouts and targeting. It does not pretend to be a stats engine.

## Targeting plus percentage

A useful composition: "beta cohort OR 10% of everyone else." Staff always on, the world ramps. Keep the rule readable. Nested AND/OR novels are how you ship to the wrong 10%. [Best practices](/blog/feature-flag-best-practices) is the hygiene list.

## After 100%

Leave the flag at 100% for a few days in case you need to kill it. Then delete the flag *and* the old branch. A flag that is 100% on forever is an `if` with extra latency.

Calendar the removal when you create the flag. Owners, not "eng."

## A rollout checklist

- Sticky hash on user id, namespaced by flag key.
- Anonymous cookie, aliased on login.
- Internal → beta → 10% → 50% → 100%, skipped only when the blast radius is tiny.
- Kill criteria written in the PR.
- Off path still in the bundle.
- Delete the flag after 100% holds.

That is percentage rollouts. The React and Next.js wiring is in [feature flags in React](/blog/feature-flags-react) and [Next.js](/blog/feature-flags-nextjs). The product that does the percentage, the targeting, and the kill switch on one meter is [Betterflag](/). [Pricing](/pricing) starts at $9.99/mo; alpha waitlist is 50% off for life.

## FAQ

### What is a percentage rollout?

A percentage rollout (also called a gradual rollout or progressive delivery) sends a feature to a fraction of users, then ramps that fraction up if it holds. Feature flags do this at request time, without a new deploy between 10%, 50%, and 100%.

### How is a percentage rollout different from a canary deploy?

A canary deploy ships a new build to a subset of servers. A percentage rollout ships one build to all servers and shows the new feature to a subset of users. Canaries catch infrastructure bugs. Rollouts catch product bugs. You often want both.

### How do you keep percentage rollouts sticky?

Hash a stable user id and compare it to the percentage. The same user stays in the same bucket as you ramp from 10% to 50%, as long as the hash is consistent. Do not pick at random per request, or users will flicker between variants.

### When should you kill a percentage rollout?

Before you start, write the kill criteria: error rate, checkout conversion, latency, a support spike. If a number crosses the line, set the flag to 0% (or off). Do not debate it during the incident. The off path has to still exist in the code.
