Feature Flags and Safe Rollouts for Fast Shipping
A feature flag lets you put code into production without turning it on for everyone. That one idea splits a release into two separate decisions: deploying the code, and exposing it to users. Once those are separate, you can ship more often, expose changes gradually and turn a bad change off in seconds instead of rolling back a deploy. Flags also add complexity, so this guide covers both how to use them well and how to keep them from piling up.
Deploying is not releasing
Without flags, every deploy is also a launch: the moment the new build is live, every user gets the new behavior. With flags, the new code path ships dark, and a separate switch decides who sees it. Google's SRE workbook chapter on canarying releases makes the same point, describing feature flag frameworks as a way to separate feature launches from binary releases.
That separation is what makes the rest of this guide possible. You can merge unfinished work behind a flag, test it in production with internal users, roll it out to a small slice of traffic, and widen it only when the numbers look right.
Four kinds of flags, four lifespans
Not every flag is the same kind of thing. Pete Hodgson's article on feature toggles, published on Martin Fowler's site in 2017, sorts them into four categories, and the category tells you how long a flag should live and how carefully to manage it.
| Type | What it is for | How long it usually lives |
|---|---|---|
| Release | Shipping incomplete or untested code paths as latent code | Short, often a week or two |
| Experiment | A/B and multivariate tests on groups of users | Hours to weeks, long enough for significant results |
| Ops | Controlling operational behavior, such as degrading a feature under load | Mostly short, though some kill switches are long-lived |
| Permissioning | Deciding which users get a feature, such as a paid tier | Can be years |
The trouble starts when these get mixed up. A release flag that quietly becomes a permanent setting, or an experiment nobody ends, turns into a branch in the code that nobody fully understands. Decide the type when you create the flag, and write it down.
Percentage rollouts and canaries
A rollout is a plan for widening exposure in steps, with a check at each step.
Percentage rollouts expose a change to a share of users, then raise the share. Two rules matter:
- Bucket on a stable key. Hash a user ID or account ID rather than rolling a random number per request, so each person gets the same answer on every request and does not flip between old and new behavior.
- Choose the steps in advance. For example, internal users first, then 1%, 5%, 25%, 50% and 100%, with a minimum time at each step. Writing the steps down removes the temptation to jump straight to everyone when the first hour looks fine.
Canary releases apply the same idea to a new build instead of a flag. The SRE workbook defines canarying as a partial and time-limited deployment of a change in a service, together with its evaluation. The part that receives the change is the canary and the rest is the control, and you compare the two before continuing. The workbook also strongly advises running only one canary deployment at a time, and notes that the canary's duration has to fit your release frequency: if you release daily, a canary cannot last a week.
Flags and canaries combine well. A canary tells you whether the new build is healthy. A flag rollout on top of it tells you whether the new feature is.
Kill switches
Hodgson describes long-lived ops toggles that can disable non-critical features when a system is under heavy load, working like manually operated circuit breakers. These kill switches are one of the few flags that should stay in the code permanently. A few practices make them dependable:
- Pick a safe default. A flag check should always carry a default value in code. Choose the value that is safe if the flag system cannot be reached, which for a new feature usually means off.
- Know who can flip it. During an incident, the person on call needs permission to flip the switch without waiting for approval, and every change should be logged.
- Exercise it. A kill switch that has never been flipped is untested code. Flip it in a staging environment on a schedule, and confirm the feature actually turns off.
Watch the rollout, not just the dashboard
A rollout is only as safe as your ability to see what it is doing. The key is to compare the users who have the change with the users who do not. The SRE workbook shows why: a problem that is invisible in the combined numbers can be obvious once metrics are broken down by population, canary versus control.
That requires recording which variant each request received. OpenTelemetry has semantic conventions for feature flags that define an event named feature_flag.evaluation, with feature_flag.key required and attributes such as feature_flag.result.variant and feature_flag.provider.name alongside it. As of September 2026, the event and those attributes carry release candidate status, so expect small changes before they are marked stable. Even before your tooling adopts them, following the names makes flag data easier to join with the rest of your telemetry.
Before you start a rollout, write down:
- The metrics that will stop it, such as error rate, latency or a key business event.
- The threshold for each one, compared against the control group rather than an absolute number.
- Who is watching, and the exact action to take: turning the flag off.
Remove old flags
Every flag is a fork in the code, and forks multiply. Hodgson describes flags as inventory with a carrying cost, and suggests several ways to keep the count down:
- add a removal task to the team's backlog as soon as a release flag is created
- put an expiration date on each flag
- add a "time bomb" test that fails if a flag is still present after its expiration date
- set a firm limit on how many flags a system may have at once
When a flag has been at 100% long enough to trust, remove it in this order: delete the old code path and its tests, ship that change, and only then delete the flag's configuration. Removing the configuration first can flip users back to the default value while the old code is still there.
OpenFeature: one API across flag vendors
Flag checks spread through a codebase quickly, which makes switching flag vendors expensive if every call site uses a vendor's own SDK. OpenFeature addresses that. It provides a shared, standardized feature flagging SDK that can be plugged into various third-party flag providers, and as of September 2026 it is a Cloud Native Computing Foundation incubating project.
Its main concepts are:
- The evaluation API, which is what application code calls to evaluate flags.
- Providers, the translation layer between that API and whichever flag management system you use.
- Evaluation context, the data flag rules evaluate against, such as a user identifier.
- Hooks, which add behavior around evaluations, such as validation, logging and telemetry.
- Events, which let code react to provider changes, including flag configuration updates.
In the Node.js server SDK, a flag check looks like this:
import { OpenFeature } from '@openfeature/server-sdk';
await OpenFeature.setProviderAndWait(new YourProviderOfChoice());
const client = OpenFeature.getClient();
const useNewCheckout = await client.getBooleanValue('new-checkout', false, {
targetingKey: user.id,
});
The provider line is the only vendor-specific part. The false is the default value, and the targetingKey in the evaluation context is the stable key that percentage rollouts should bucket on. The OpenFeature specification requires that flag evaluation calls return the default value when evaluation goes wrong rather than throwing, which is exactly why choosing a safe default matters.
The most useful habit to take from all of this: create the removal ticket in the same pull request that adds the flag. A flag with a planned end is a rollout tool; a flag without one is permanent complexity.