In plain English
A feature flag separates deploying code from releasing behaviour. The new path ships to production disabled, and a runtime configuration decides who sees it — everyone, nobody, 10% of users, or one arm of an experiment. That separation is what makes continuous deployment compatible with careful release, and it is the delivery layer nearly every experimentation platform is built on.
Three distinct jobs run on the same infrastructure and they are worth keeping separate in your head. A release flag rolls something out gradually, watching error rates as the percentage climbs — that is risk management, not measurement, and it has no control group. An experiment flag assigns users randomly and holds the assignment stable for the duration, which is what makes a comparison valid. A kill switch exists to turn something off immediately when it misbehaves. Conflating the first two is the common error: a gradual rollout tells you the system did not fall over, and nothing about whether the change was any good.
For experimentation the requirements on the flag are specific. Assignment must be deterministic — the same user gets the same variant on every request and every device, or their experience is inconsistent and their data uninterpretable. It must be based on a stable identifier rather than a session, and it must be recorded, since an experiment whose assignments were not logged cannot be analysed at all.
The mistake that does the most damage is changing a flag's configuration mid-experiment. Increasing the rollout from 10% to 50% halfway through means the later users were assigned under different conditions and joined at a different point in the calendar, so the two arms are no longer comparable — this is a common cause of sample ratio mismatch, and it usually invalidates everything collected before the change. If the allocation must change, the honest response is to restart the experiment.
There is also an operational cost that experimentation teams inherit. Flags accumulate: every one is a branch in the code, and a codebase with hundreds of stale flags has an untestable number of possible states. Flags that outlive their purpose should be removed and the losing branch deleted, which sounds obvious and is one of the most consistently neglected pieces of hygiene in experimentation-heavy organisations.
The formula
Nothing here is statistical. What matters is the assignment function, which must be deterministic, and the failure mode when it changes mid-flight.
- Deterministic assignment
variant = hash( user_id + experiment_id ) mod 100 < allocationSame input, same output, on every request and device. Including the experiment id keeps concurrent tests independent.
- Why the experiment id is in the hash
without it, every test assigns the same users to the same armCorrelated assignment across experiments, which makes interactions systematic rather than random — see interaction effect.
- What a mid-test change does
arms differ in join date as well as in treatmentTime becomes a confounder and the split usually fails a chi-square SRM check — see the chi-square calculator.
- Rollout is not measurement
gradual release has no control groupIt answers whether the system stayed up, not whether the change helped.
Worked example
A team launches an experiment at a 10% allocation to watch for errors, sees none after two days, and raises it to 50% to finish faster. The test runs a further twelve days and reports a 4.1% lift on conversion. A reviewer runs a sample ratio check before it ships.
- Days 1-2
- 10% treatment, 10% control, 80% untracked
- Days 3-14
- 50% treatment, 50% control
- Total control users
- 184,200
- Total treatment users
- 197,400
- Expected split
- 50/50
- SRM chi-square
- 462.3, p < 10⁻¹⁰⁰
The reported 4.1% lift cannot be trusted. The arms differ by 13,200 users, which no random split would produce.
The imbalance comes from the reallocation rather than from any bug in the assignment layer. When the percentages changed, the hashing boundaries moved, so some users who had been in control on day two found themselves in treatment on day three — and those users carry two days of control behaviour into the treatment arm's data. The arms now differ in composition and in when their members joined, and there is no analysis that separates that from the treatment effect. Two things to take from it. The instinct behind the change was sound: starting small to watch for errors is good practice. The error was doing it inside a running experiment rather than as a separate release phase that concludes before the experiment starts. And the SRM check is what caught it — a 3.4% imbalance is invisible by eye and overwhelming statistically, which is exactly why that check should run automatically on every experiment rather than when someone thinks to look.
Common misconceptions
- דRolling a feature out gradually is a form of testing it.”
- It is risk management. A staged rollout tells you the system did not break as load increased, and it has no control group, so it cannot tell you whether the change helped. Measuring impact needs a concurrent randomised comparison; the two activities use the same flag infrastructure and answer different questions.
- דIncreasing the allocation mid-test just gets you to significance faster.”
- It changes which users are in which arm, so the arms differ in composition and in join date as well as in treatment. This is a common cause of sample ratio mismatch and usually invalidates the data collected beforehand. If the allocation must change, restart the experiment.
- דOnce a feature is fully rolled out the flag can stay for safety.”
- Every flag left in place is a branch that must keep working, and a codebase with hundreds of them has a number of possible states nobody can test. Keep a kill switch deliberately and briefly if the change is risky; remove the flag and delete the losing branch once it has proven itself.