Experiment Design and A/B Testing
Peeking and sequential testing
Checking your experiment repeatedly and stopping the moment it looks significant quietly multiplies your false alarms — sequential testing is how to look early without lying.
- 8 min read
- 3 reading levels
- Published
Read these first
On this page 5
One lesson, three depths. Pick the one that fits you today — you can switch any time.
Beginner — No maths. Plain English.
Peeking means checking your experiment again and again, and stopping the moment the result looks good — which makes lucky streaks look like real wins.
Think of baking a cake and opening the oven door every two minutes. The moment the top looks golden, you pull it out — and the inside is raw. The cake needed its full time, and so does an experiment. Looking early is not the sin. Acting on the early look is.
Here is the trap in plain words. Random luck wobbles up and down. If you watch the wobble long enough and pounce on any upward blip, you will catch one — even when nothing real is happening.
Why it exists as a problem
Experiment dashboards update live. A product manager watches the numbers every morning. On day four the new design is "ahead", so the test is stopped and declared a win. It felt harmless.
But the statistics were built on a promise: one look, at a fixed time. Checking daily gives luck many chances to cross the finish line. A meaningless wobble gets crowned as a discovery, the feature ships, and the promised gain never appears in the yearly numbers.
How it works
truth: no difference at all
day 1 day 2 day 3 day 4 day 5 day 6
+2% -1% +3% +4%! +1% -2%
↑
stop here → "winner!" (it was noise)The honest fixes come in two flavours. Either decide the sample size in advance and look once, at the end. Or use sequential testing — statistics redesigned to expect many looks. It sets a stricter bar at each look, so the total false alarm rate stays where you promised.
A real example you have seen
Medicine solved this decades ago. Drug trials have planned "interim analyses": scheduled early checks with stricter thresholds. A truly harmful or miraculous drug stops the trial early, without letting luck decide. Modern experiment platforms like Optimizely rebuilt their statistics around the same idea after discovering users peeked constantly.
Remember this
- Watching a wobbling number and stopping on a good day manufactures false wins.
- Fix the sample size in advance and look once — or use sequential tests built for looking.
- Wanting to stop early is not a character flaw; it needs the right statistics, not willpower alone.
What to learn next
- Sample ratio mismatch — the daily check that is not peeking, and must happen.
- Bayesian A/B testing — a different lens on stopping, with its own honest limits.
- Multi-armed bandits — when you want the machine to shift traffic while learning.
Developer — Code and libraries.
Setup
pip install numpy scipyVerified with numpy 1.26.4 and scipy 1.14.1.
Measuring the damage peeking does
Both arms below are identical — no real effect exists. One analyst waits for all 5,000 users per arm. The other checks after every 500 and stops at the first "significant" p-value.
import numpy as np
from scipy import stats
rng = np.random.default_rng(7)
def one_experiment(peek: bool) -> bool:
a = rng.normal(100, 25, 5000)
b = rng.normal(100, 25, 5000) # identical worlds: no real effect exists
if not peek:
return stats.ttest_ind(a, b).pvalue < 0.05
for n in range(500, 5001, 500): # look every 500 users, stop on a win
if stats.ttest_ind(a[:n], b[:n]).pvalue < 0.05:
return True
return False
runs = 1000
patient = sum(one_experiment(peek=False) for _ in range(runs))
peeker = sum(one_experiment(peek=True) for _ in range(runs))
print(f"false alarms with one look at the end: {patient / runs:.1%}")
print(f"false alarms when peeking ten times: {peeker / runs:.1%}")false alarms with one look at the end: 5.3% false alarms when peeking ten times: 18.3%
The walkthrough
5.3% versus 18.3%. The patient analyst pays the advertised false alarm rate. The peeker's rate more than triples — with only ten looks. Platforms that recompute significance on every dashboard refresh are effectively taking hundreds of looks.
Why it happens: each look is a fresh chance for noise to cross the 0.05 line. The p-value sequence wanders like a drunk walking near a ditch. Give the walk enough steps and it falls in somewhere — and peeking means treating the first stumble as the verdict.
The stopped estimate is also inflated. Experiments stopped on a lucky spike report that spike as the effect size. Shipping decisions based on early stops systematically overpromise — this is one source of the "our wins never add up" complaint, alongside the fading effects in novelty and primacy.
Honest ways to look early
Group sequential tests. Plan the looks (say, five), and spend your 5% false alarm budget across them. The O'Brien–Fleming schedule makes early looks very strict and the final look nearly normal. scipy has no built-in; the statsmodels ecosystem and dedicated packages cover it.
Always-valid p-values. A newer approach (mixture sequential probability ratio tests) produces p-values that stay honest under continuous monitoring — check every second if you like. This is what several commercial platforms now run underneath.
A blunt but effective correction: with $k$ planned looks, test each at level $0.05/k$ (Bonferroni). It overcorrects, costing some power, but never lies.
Common mistakes
"I'll only stop early if it's a big win." That policy is exactly the simulated peeker. The size of the tempting number does not repair the broken error rate.
Peeking, continuing, then reporting the final p-value as if unwatched. The damage is baked in the moment stopping could have happened based on the data. The analysis must match the stopping rule you actually followed.
Confusing monitoring with testing. Watching dashboards for bugs, outages or sample ratio mismatch is essential daily work. The line is crossed when the treatment-effect number influences when you stop.
Restarting "unlucky" experiments. Killing tests that start badly and relaunching them is peeking wearing a moustache. The relaunched wins are survivor luck.
Try it yourself
Change the peeker to look after every 100 users — fifty looks — and measure the false alarm rate. Then try the Bonferroni repair: with ten looks, test each at 0.005 and confirm the overall rate lands near or below 5%.
What to learn next
- Sample ratio mismatch — the daily check that is not peeking, and must happen.
- Bayesian A/B testing — a different lens on stopping, with its own honest limits.
- Multi-armed bandits — when you want the machine to shift traffic while learning.
Researcher — Mathematics and papers.
Why the naive sequential test must fail
Under $H_0$, the standardised difference $Z_n$ behaves asymptotically like a standard Brownian motion observed at increasing times (rescaled). The law of the iterated logarithm gives
$$ \limsup_{n \to \infty} \frac{|Z_n|}{\sqrt{2 \log \log n}} = 1 \quad \text{a.s.} $$
- $Z_n$ — the z-statistic after $n$ observations per arm.
- $\limsup$ — the largest value the sequence keeps returning near, forever.
Since $\sqrt{2 \log \log n} \to \infty$, the statistic crosses any fixed boundary $\pm z_\alpha$ with probability 1 given unlimited looks. Continuous monitoring against a fixed threshold has a false positive rate of 100%, not 5%.
Group sequential designs
Armitage, McPherson and Rowe (1969) quantified the inflation; Pocock (1977) and O'Brien and Fleming (1979) gave usable boundary schedules. Lan and DeMets (1983) generalised them to an alpha-spending function $\alpha(t)$: a non-decreasing function of information fraction $t \in [0,1]$ with $\alpha(1) = \alpha$, dictating how much type-I error may be spent by each look. O'Brien–Fleming spends almost nothing early — boundaries near $4\sigma$ at $t = 0.2$ — preserving power for the final analysis.
Always-valid inference
Johari, Koomen, Pekelis and Walsh (2017), Peeking at A/B Tests: Why It Matters, and What to Do About It (KDD), industrialised the mixture sequential probability ratio test (mSPRT). For a mixture distribution $H$ over effect sizes, the statistic
$$ \Lambda_n = \int \prod_{i=1}^{n} \frac{f_\theta(x_i)}{f_0(x_i)} \, dH(\theta) $$
- $f_\theta$ — the likelihood under effect $\theta$; $f_0$ — under the null.
- $\Lambda_n$ — a nonnegative martingale under $H_0$ with $\mathbb{E}[\Lambda_n] = 1$.
By Ville's maximal inequality, $\Pr(\sup_n \Lambda_n \ge 1/\alpha) \le \alpha$ — so rejecting when $\Lambda_n$ exceeds $1/\alpha$ is valid at every stopping time. The modern generalisation is anytime-valid inference via e-values and confidence sequences: Howard, Ramdas, McAuliffe and Sekhon (2021), Time-uniform, nonparametric, nonasymptotic confidence sequences, Annals of Statistics; Ramdas et al. (2023), Game-theoretic statistics and safe anytime-valid inference, Statistical Science.
The price and the bias
Sequential validity costs width: confidence sequences are wider than fixed-$n$ intervals at the same $n$ (an inflation factor growing like $\sqrt{\log \log n}$ at worst). And any design that stops early on extremes suffers winner's-curse bias in the reported effect; conditional-bias corrections exist (Whitehead, 1986) but are rarely applied in industry dashboards. Bandit-style adaptive allocation is the further step beyond fixed splits — see multi-armed bandits.
What to learn next
- Sample ratio mismatch — the daily check that is not peeking, and must happen.
- Bayesian A/B testing — a different lens on stopping, with its own honest limits.
- Multi-armed bandits — when you want the machine to shift traffic while learning.