Chapter 19 · Statistics and ML

A/B Testing and Experimentation

The only technique in this course that establishes causation. Everything else measures what happened; a randomised experiment tells you what your change caused — provided you avoid a long list of ways to break it.

24 sections statsmodels 0.14 Worked experiment 12 interview drills Reading time ~3 hours

[!] The uncomfortable industry result

Large experimentation programmes at Microsoft, Google, Netflix and Booking.com have all reported the same finding: somewhere between two thirds and 90% of ideas fail to improve the metric they targeted, and a meaningful fraction actively harm it. These are not bad teams. They are teams measuring properly.

That result reframes what experimentation is for. It is not a formality to confirm a good idea; it is the mechanism that stops you shipping the majority of your ideas, which do not work. A team whose experiments almost always succeed is not unusually talented — it is measuring something wrong, peeking, or only testing changes it already knew were safe.

[i] What this assumes, and what it doesn't repeat

Chapter 17 supplies the machinery: confidence intervals (17.9), power and minimum detectable effect (17.15), multiple comparisons and peeking (17.16), and Simpson's paradox (17.18). This chapter is about the operational layer that sits on top — the failures that occur because an experiment runs on live traffic against real users rather than in a notebook.

The distinction matters because the statistics are the easy part. Sample ratio mismatch, novelty effects, interference and the metric that improves while revenue falls are all engineering and product problems that no amount of correct arithmetic will rescue.

19.1 What randomisation buys you

Section 17.19 ranked the evidence for causation and put randomised experiments at the top. This section explains why the gap between it and everything else is so large.

[def] The property no observational method has

Randomisation balances confounders you did not think of. Assign users to arms at random and device type, tenure, market, browsing habit, income and every unmeasured variable are balanced in expectation. No amount of statistical adjustment achieves this, because adjustment can only correct for variables you identified and measured. That single property is why a modest experiment beats an elaborate observational study on a hundred times the data.

[retail] Why "we launched it and revenue went up" proves nothing

Revenue rose 4% the week after the new ranking model shipped. So did a marketing email, a competitor's stock-out, and the start of a seasonal peak. Without a control group running simultaneously, the effect of your change is inseparable from everything else that happened that week. Before-and-after comparison is not evidence, and the difference between "launched and revenue rose" and "revenue rose because of the launch" is the entire content of this chapter.

19.2 When not to run an experiment

Experiments cost traffic, calendar time and engineering effort. A mature programme is as clear about when to skip one as when to insist.

Legitimate reasons not to test
Situation Why
Not enough traffic If detecting your minimum effect needs eleven weeks, the result arrives after the decision. Ship on judgement and monitor.
Legal, security or compliance You do not A/B test whether to fix a vulnerability. The change is mandatory, so the experiment has no decision to inform.
Obviously broken Fixing a bug that returns errors does not need a control group receiving errors.
Harmful to the control arm Withholding a genuine safety improvement to measure it precisely is an ethics problem, not a rigour problem.
Effect far too small to matter If the best plausible outcome is below your minimum detectable effect, the experiment can only produce a null result.

[!] The bad reason, which is the common one

"We already know it works." That belief is exactly what the industry data contradicts — most confident ideas fail when measured. If a team consistently skips experiments because the outcome seems obvious, it is not saving time; it is protecting its assumptions from contact with evidence, and it will accumulate a codebase full of changes that never helped and can no longer be untangled.

19.3 The anatomy of a test

the shape of every experimenttext
1  HYPOTHESIS      a falsifiable claim, written before launch
2  UNIT            what gets randomised (user? session? request?)
3  ASSIGNMENT      deterministic hash -> arm, stable across visits
4  METRICS         one primary, a few guardrails, rest exploratory
5  SAMPLE SIZE     computed from the minimum effect worth acting on
6  A/A CHECK       does the pipeline report "no difference" correctly?
7  RUN             fixed duration, whole weeks, no peeking
8  VALIDITY        sample ratio mismatch, instrumentation, novelty
9  ANALYSE         effect size, interval, then the p-value
10 DECIDE          ship, kill, or iterate -- decided in advance

[+] Steps 1, 5 and 10 happen before launch, and that is the whole discipline

Writing the hypothesis, the sample size and the decision rule in advance is what makes the result trustworthy, because it removes every degree of freedom you would otherwise exercise after seeing the data. An experiment where the success criterion is decided afterwards is not an experiment; it is a search for a favourable interpretation, and it will nearly always find one (17.16).

19.4 Choosing the randomisation unit

What to randomise on, and the consequences
Unit Use when Cost
User Almost always. Consistent experience across visits and devices. Fewer independent units, so less statistical power per unit of traffic.
Session When the change is confined to one visit and cross-visit consistency does not matter. The same person may see both variants, which can confuse and contaminate.
Request Backend changes with no visible difference — a caching or infrastructure test. Maximum power, and invalid for anything a user could notice.
Cluster (store, region, market) When effects spill between users, or the change is inherently geographic. Very few units, so power collapses. Often needs a switchback design (19.19).

[!] Analyse at the unit you randomised

Randomise by user and you must analyse by user. Computing significance over sessions when users were assigned treats one heavy user's twenty sessions as twenty independent observations, which they are not. The result is understated variance and a confident conclusion built on double-counting — and it is one of the most common analysis errors in real experimentation platforms.

19.5 Assignment that actually works

assignment.pypython
import hashlib

def assign(user_id: str, experiment: str, arms: int = 2) -> int:
    """Deterministic, stateless, independent across experiments."""
    key = f"{experiment}:{user_id}".encode()
    digest = hashlib.sha256(key).hexdigest()
    return int(digest[:8], 16) % arms

[+] Why hashing rather than a random number

  • Deterministic. The same user gets the same arm on every request, with no lookup and no stored state.
  • Salted per experiment. Including the experiment name means a user unlucky in one test is not systematically in the same arm of the next — without the salt, concurrent experiments become correlated.
  • Stateless. Any server computes it identically, so there is no assignment service to fail and no cache to go stale.

[!] Where assignment quietly breaks

  • Logged-out users. A cookie-based ID changes when cookies clear, so the same person can be reassigned mid-experiment. Unavoidable, but it dilutes the effect and should be acknowledged.
  • Login mid-session. Assignment on an anonymous ID and then on a user ID can flip the arm mid-visit. Decide which identity governs and stick to it.
  • Assignment before eligibility. Assigning every visitor when only checkout users can experience the change buries a real effect under a crowd of unaffected people. Assign at the point of eligibility, and this alone often doubles effective power.
  • Redirects. Sending the treatment arm through an extra redirect adds latency to one arm only. You are now measuring your change plus a slowdown.

19.6 One primary metric, and guardrails

The single most effective defence against fooling yourself is deciding, before launch, which number decides the outcome — and committing to it when it disappoints.

[def] Three tiers of metric

  • Primary. Exactly one. It decides ship or kill, and it is named in the hypothesis before launch.
  • Guardrails. A small fixed set that must not degrade: revenue, latency, error rate, complaint volume. Tested one-sided, because you only care if they get worse.
  • Exploratory. Everything else. Generates hypotheses for future experiments and never justifies shipping on its own (17.16).

[retail] Guardrails catch the wins that are actually losses

A new ranking model raises click-through by 6%. Revenue is flat and returns rise 3%. What happened: the model learned to promote visually appealing products that convert well and come back. Click-through — the metric the team chose — improved exactly as predicted, and the business is worse off. Without the returns guardrail this ships as a success, and the damage surfaces a quarter later as a margin problem nobody connects to a ranking change.

19.7 Proxy metrics and their dangers

The metric you truly care about is often too slow or too noisy to use, so you substitute something faster. That substitution is necessary and is where a great many experiments go wrong.

Why proxies get used, and how they fail
You care about You measure How the proxy lies
Customer lifetime value Conversion this session Discounting raises conversion and destroys margin and future full-price purchases.
Search satisfaction Click-through rate Clickbait titles raise clicks. So does a bad result page that forces repeat searching — more searches per session can mean failure, not engagement.
Answer quality Thumbs-up rate Only a tiny, unrepresentative fraction of users ever rate anything, and they rate tone as much as correctness.
Long-term retention Seven-day return rate Notification spam improves the seven-day number and increases uninstalls at day thirty.

[!] Goodhart's law is the whole problem

When a measure becomes a target, it stops being a good measure. This is not cynicism: optimisation pressure finds whatever satisfies the metric most cheaply, and that is rarely the thing the metric was standing in for. Validate the proxy before trusting it — run one experiment where you can measure both the proxy and the real outcome, and confirm they actually move together. Teams almost never do this, and then spend years optimising a number whose relationship to the business was assumed rather than demonstrated.

19.8 The hypothesis, written down

experiment brief, written before launchtext
BECAUSE     query logs show 31% of searches with zero results are
            misspellings of stocked products

WE BELIEVE  adding fuzzy matching to the retrieval stage

WILL        increase search-to-cart rate for affected sessions
            from 3.0% to at least 3.3%  (+10% relative)

MEASURED BY search-to-cart rate, user-randomised, 14 days

WE WILL SHIP IF   primary metric improves and CI excludes zero
                  AND p95 latency does not rise above 300ms
                  AND return rate does not rise

WE WILL KILL IF   no significant improvement, or any guardrail breached

[+] The value is in the "because" and the "we will kill if"

The because forces a mechanism — you must state why the change should work, which frequently exposes that nobody knows. The kill criterion is the part teams omit, and it is the one that matters: agreeing the failure condition in advance is what prevents the post-hoc reinterpretation that turns every null result into "directionally positive". Written before launch, it costs nothing. Written after, it is negotiation.

19.9 Sample size and duration

[def] Minimum detectable effect

The MDE is the smallest effect the experiment can reliably detect, and it should be set from the business side: the smallest lift that would justify shipping and maintaining the change. Everything else in the calculation follows from it.

[retail] The cost of chasing smaller effects

3% baseline conversion, 80% power, alpha 0.05text
relative lift      per arm        total      days at 600k/day
    20%             13,887        27,773            0.05
    10%             53,182       106,364            0.2
     5%            207,908       415,816            0.7
     2%          1,281,161     2,562,322            4.3

Halving the effect you want to detect multiplies the traffic needed by almost exactly four — the inverse-square relationship from 17.8, now expressed as a project schedule. Note also what this table says about a platform at this scale: power is rarely the binding constraint. Even a 2% relative lift is detectable in under a week, which means the real limits on experiment velocity are engineering time and the minimum sensible duration, not sample size.

[!] Run whole weeks, always

Behaviour differs by day. A test running Monday to Friday measures weekday users only, and if it happens to end mid-weekend the two arms may not even cover the same days. Round up to complete weeks — and prefer two weeks to one, because a single week cannot distinguish a real effect from an unusual week. If the sample size calculation says four hours, run a week anyway; the cost is negligible and the protection against a payday, a promotion or an outage is real.

[+] When the answer is "eleven weeks"

That is a useful result, not a failure — it tells you the effect you are chasing is too small to measure at your traffic. The options are to test a bolder change with a larger expected effect, use variance reduction to buy precision (19.17), pick a more sensitive metric closer to the change, or accept the risk and ship on judgement with monitoring. What you must not do is run it for two weeks anyway and interpret the result, which is guaranteed to be underpowered (17.15).

19.10 The A/A test

Before trusting a platform to tell you a change worked, confirm it can correctly tell you that nothing happened. An A/A test ships identical experiences to both arms and should find no difference.

[+] What an A/A test actually catches

  • Broken assignment. If the split is not 50/50, the hashing or the traffic router is wrong (19.11).
  • Instrumentation differences. If the treatment arm logs through a different path, it may drop or duplicate events — and you would otherwise attribute that to your change.
  • Understated variance. Run many A/A tests and about 5% should show significance at α = 0.05. If 15% do, your analysis is treating correlated observations as independent (19.4) and every future result is overconfident.
  • Pre-existing bias. If the arms differ before the change, they will differ after it, and you will never know which part was yours.

[!] One A/A test proves very little

A single A/A run showing no difference is weak evidence — it should show no difference 95% of the time even when the platform is subtly broken. The value comes from running them continuously: a permanent A/A pair in the background, whose false positive rate you track over months. That is how mature platforms detect variance understatement, which is invisible in any individual experiment.

19.11 Sample ratio mismatch

[def] SRM, and why it invalidates everything

You configured a 50/50 split. If the arms receive materially different numbers of users, something is systematically excluding people from one arm — and whatever that something is, it almost certainly correlates with behaviour. A sample ratio mismatch invalidates the entire experiment, no matter how good the primary metric looks. It cannot be corrected in analysis, only diagnosed and fixed.

[retail] Telling noise from a problem

chi-square test on the splittext
control / treatment      chi-square       p        verdict
  50,214 / 49,786            1.83      0.176      normal variation
  51,200 / 48,800           57.60      3.2e-14    STOP AND INVESTIGATE

The second split is 51.2% to 48.8% — a 2.4% imbalance that looks trivial and is overwhelming evidence of a bug at this sample size. That intuition gap is the point: eyeballing the numbers will not catch SRM, so the chi-square test belongs in the experiment dashboard as an automatic check, with an alert threshold around p < 0.001.

[+] The usual causes, in the order I would check them

  1. Treatment errors out for some users Those sessions fail before logging exposure, so the treatment arm loses exactly the users who hit the bug — a survivorship bias (17.6) that also removes the evidence of itself.
  2. Redirect or latency asymmetry An extra hop in one arm loses impatient users before the page loads.
  3. Bot filtering applied after assignment If bots are removed post-hoc and they do not distribute evenly, the arms diverge.
  4. Assignment logged at a different point in each arm The most common cause and the most boring: the two arms are not counting the same moment.

19.12 Novelty and primacy effects

Two opposite time-dependent biases
Effect What happens Signature
Novelty Users engage with a change because it is new, not because it is better. Large early lift that decays toward zero over one to two weeks.
Primacy Existing users are slowed by having to relearn a familiar interface. Early dip that recovers as people adapt. Punishes genuinely better designs.

[+] How to distinguish them from a real effect

Plot the daily effect over time rather than only the cumulative number — a real effect is roughly flat, while novelty and primacy have obvious slopes. Better still, split by new versus returning users: novelty and primacy are both about familiarity, so a change that helps brand-new users and hurts existing ones is showing primacy, and one that excites existing users while doing nothing for new ones is showing novelty. Users who have never seen the old version cannot experience either.

19.13 Peeking, and what to do instead

Section 17.16 established the cost: checking twenty times takes the false positive rate from 5% to roughly 25%. The practical question is what to do about it, since "do not look at the data for two weeks" is not a workable instruction for a real team.

Three legitimate approaches
Approach How it works Trade-off
Fixed horizon Compute the sample size, run to it, analyse once. Simplest and most powerful per unit of traffic. Requires actual discipline.
Group sequential A small number of pre-planned interim looks with adjusted boundaries. Allows early stopping for a large effect; the boundaries must be set in advance.
Always-valid inference Sequential tests and confidence sequences that remain valid under continuous monitoring. Look whenever you like — at the cost of needing more data for the same power.

[+] The pragmatic arrangement

Let everyone watch the guardrails continuously — you genuinely do want to stop early if latency doubles or errors spike, and stopping for harm does not inflate your false positive rate for the primary metric. Then hide the primary metric until the planned end, or display it only with always-valid intervals. Separating "is it broken?" from "is it better?" resolves most of the tension, because the impulse to peek is usually anxiety about the first question.

19.14 Interference between units

[def] When one user's treatment affects another's outcome

Standard analysis assumes each unit's outcome depends only on its own assignment. Interference breaks that assumption, and when it is present the control group is no longer a clean baseline — it has been contaminated by the treatment. The measured difference then understates, overstates, or entirely misrepresents the effect.

[retail] Three ways this happens in a marketplace

  • Shared inventory. Treatment users buy the last unit of a popular product, so control users see it out of stock. The treatment looks good partly by making the control worse — a zero-sum effect that vanishes at full rollout.
  • Shared models. If a ranking model retrains on live behaviour, treatment-arm clicks influence what control users are shown. The arms are no longer independent, and this is easy to miss because the coupling is inside a training pipeline (18.19).
  • Social and network effects. A sharing feature only works if the recipient also has it. Testing it on a random 50% measures a crippled version of the feature.

[+] The fixes, each with a real cost

Cluster randomisation assigns whole regions or markets, which contains the spillover but leaves you with very few independent units and correspondingly poor power. Switchbacks alternate the whole system between variants over time (19.19), which works well for marketplace and pricing changes. And sometimes the honest answer is a staged rollout with a holdout: accept that a clean A/B is not available and measure against a small permanently untreated group instead (19.20).

19.15 Reading the result honestly

The analysis itself is the easy part — chapter 17 supplied it. What takes discipline is reading the output without reaching for the interpretation you were hoping for.

[+] The order to read them in

  1. Validity checks first, before looking at the result Sample ratio (19.11), instrumentation parity, and whether the effect is stable over time (19.12). If any of these fail, the primary metric is meaningless and reading it will only contaminate your judgement.
  2. Guardrails second A breached guardrail kills the experiment regardless of how good the primary looks.
  3. Then the primary metric, as an interval The absolute difference, the relative lift, and the confidence interval — in that order, with the p-value last (17.14).
  4. Exploratory metrics last, and labelled as such They generate next experiments. They never justify shipping.

[!] Statistically significant is not the same as worth shipping

A significant +0.4% lift with a confidence interval of [0.05%, 0.75%] is a real effect whose plausible size ranges from negligible to modest. Shipping decisions should weigh the lower bound against the cost of maintaining the change, because that is the conservative case. If the change adds a service dependency and the pessimistic outcome is 0.05%, the honest answer may be to reject a statistically significant win.

19.16 Segmentation without fooling yourself

Slicing results by device, market, tenure or traffic source is where genuine insight and self-deception are hardest to tell apart.

[!] Ten segments is ten more chances to find nothing

Testing a flat result across ten segments gives roughly a 40% chance of at least one significant slice by luck alone (17.16). The story then writes itself — "it works for mobile users in Mexico" — and it is usually noise. The tell is that nobody predicted that segment beforehand, and no mechanism explains it.

[+] What makes a segment finding credible

  • Pre-registered. You said before launch that mobile might differ, and why.
  • Mechanistically sensible. There is a reason the change should affect that group more — a small screen, a slower connection, a different catalogue.
  • Large and consistent. Not a marginal p-value, and visible across the whole run rather than in one week.
  • Replicated. The only genuinely convincing evidence is a follow-up experiment targeting that segment specifically.

[retail] Remember which direction the paradox runs

Section 17.18 showed a variant winning in both segments and losing overall. In a properly randomised experiment that pattern is a symptom of broken assignment rather than an interesting insight, because randomisation should have balanced the device mix. So if pooled and segmented results disagree, check the sample ratio in each segment before writing any analysis — you are more likely looking at an SRM than at a subtle behavioural finding.

19.17 CUPED and variance reduction

If you cannot get more traffic, the alternative is to make each user count for more by removing variance you can explain.

[def] Using the past to cancel out noise

CUPED — controlled experiment using pre-experiment data — adjusts each user's outcome by their own behaviour before the experiment started. A user who always spends heavily is expected to spend heavily in both arms, so that predictable component is noise for our purposes. Subtracting it leaves a tighter estimate of the treatment effect, and because pre-period data cannot be influenced by a treatment that has not happened yet, the adjustment introduces no bias.

[retail] Measured, on simulated spend data

50,000 users per armtext
correlation(pre-period, post-period) = 0.759

standard error, plain     0.4142
standard error, CUPED     0.2698

variance reduction  57.6%      (theory says r^2 = 57.6%)
equivalent to 2.36x the sample size

The variance reduction equals the squared correlation almost exactly, which is the useful rule: a pre-period metric correlated at 0.7 with the outcome removes about half the variance, worth roughly a doubling of traffic. An eleven-week experiment becomes a five-week one for the cost of a join.

[!] Where it does not help

CUPED needs a pre-period metric that correlates with the outcome, which rules out new users — they have no history — and metrics that are rare or unstable per user. It also cannot rescue a broken experiment: it reduces variance, not bias, so an SRM or an interference problem is exactly as fatal afterwards. Use the same metric from the prior period where possible; that is almost always the strongest available covariate.

19.18 When the result is flat

Most experiments produce no significant effect. Treating that case well is what separates a functioning experimentation culture from a theatrical one.

Four reasons for a null result, and what each implies
Reason How to tell Response
The change genuinely does nothing Adequate power, tight interval around zero. Believe it. Remove the code — unused variants are permanent complexity.
Underpowered Wide interval that includes effects you would have shipped for. Inconclusive, not negative. Run longer or reduce variance (19.17).
Diluted Only a small fraction of assigned users could experience the change. Re-run, assigning at the point of eligibility (19.5).
Offsetting effects Flat overall, opposite directions in pre-registered segments. The interesting case. Two real effects cancelling, worth understanding before iterating.

[+] A flat result is information worth paying for

It tells you a plausible idea does not work, which saves effort that would otherwise have been spent maintaining it. The failure mode is a team that treats null results as embarrassing, because that team will start searching segments for something to report — and it will find something. Record null results as findings, with the interval, so nobody re-runs the same idea in eighteen months.

19.19 Switchback, interleaving, bandits

Three designs that solve problems a standard user-randomised A/B cannot, each with a specific situation it belongs to.

When the simple design will not do
Design Mechanism Use for
Switchback Alternate the whole system between variants in short time blocks, then compare blocks. Marketplace, pricing and logistics changes where users interfere with each other (19.14). Randomising time rather than users.
Interleaving Blend results from both rankers into a single list and see which source gets clicked. Ranking comparisons. Every user is their own control, which makes it dramatically more sensitive than an A/B.
Multi-armed bandit Shift traffic toward whichever arm is performing better as evidence accumulates. Short-lived decisions with many arms, where the cost of showing a loser is high. Not for measurement.

[+] Interleaving is the right tool for chapter 5's problem

Comparing two rerankers by A/B needs enormous traffic, because between-user variation swamps the difference — the same reason 17.13's paired test beat the unpaired one. Interleaving removes that variation entirely by showing both rankers' results to the same user in one list, so the only thing differing is the ranking. Published comparisons put it at roughly an order of magnitude more sensitive than a conventional A/B test. The limitation is that it only answers "which ranking is preferred", not "what does this do to revenue".

[!] Bandits optimise, they do not measure

A bandit maximises reward during the test by starving the losing arm of traffic. That is precisely what makes it a poor measurement tool: you end up with a confident estimate for the winner and a vague one for everything else, and the adaptive allocation breaks the assumptions behind standard confidence intervals. Use a bandit when you want the best outcome during a short campaign; use an A/B when you need to know the effect size for a lasting decision.

19.20 Holdouts and long-term effects

[def] The permanent control group

A holdout is a small slice of users — typically 1–5% — kept on the old experience for months, long after individual experiments have shipped. It answers a question no single experiment can: what has a quarter's worth of accumulated changes actually delivered, together?

[!] Why individual wins do not add up

Ten experiments each measured at +1% do not produce +10%. They overlap, they interact, some effects were novelty that decayed after launch, and some were false positives that were never real. Teams that only run short experiments routinely report annual gains that the business results do not show — and the holdout is the only instrument that exposes the gap. It is also the least popular measurement in any organisation, because it occasionally proves a quarter of work was worth less than claimed.

[+] Practical constraints

Keep it small and rotate it periodically, so no individual is stuck on a degrading experience indefinitely. Accept that it limits what you can ship — some infrastructure changes cannot maintain an old path — and budget for those explicitly rather than quietly. And be careful interpreting it against a long window: the holdout also misses bug fixes and performance work, so the comparison is not purely about features.

19.21 Experimenting on LLM features

Everything so far assumed a deterministic change. An LLM feature is non-deterministic, expensive per call, and produces output that cannot be scored automatically — which breaks several assumptions at once.

What changes, and what to do about it
Problem Consequence Approach
Non-deterministic output The same user with the same query gets different responses, adding variance unrelated to your change. Fix temperature and seed where the provider supports it; otherwise accept lower power and size accordingly.
Quality is not directly measurable No automatic score for "was this answer good". Use behavioural proxies — task completion, follow-up rate, escalation to human — plus offline judging on a sample (5.33).
Cost per call is significant The treatment arm has a real marginal cost the control does not. Make cost per session an explicit guardrail. A 2% conversion lift that doubles inference spend may not be a win (16.18).
The model changes underneath you A provider updates the model mid-experiment and the treatment silently becomes a different treatment. Pin model versions for the entire run. Treat an unpinned provider as an uncontrolled variable.
Latency asymmetry Generation takes seconds; the control returns instantly. You are testing the feature and a slowdown. Stream the response (9.17) and monitor time-to-first-token as a guardrail.

[+] Offline evaluation first, online experiment second

Chapter 5's evaluation set and chapter 8's trajectory scoring are cheap, fast and repeatable; an A/B test is expensive and slow. Use offline evaluation to reject bad variants and to choose which version is worth testing, then use the experiment to measure what it does to real behaviour. Running an A/B on a prompt change you could have rejected offline in ten minutes is a waste of two weeks of traffic.

19.22 A worked experiment, end to end

[retail] Adding AI-generated summaries to search results

experiment brieftext
BECAUSE     32% of long-tail queries end without a click, suggesting
            users cannot tell which result answers their question

WE BELIEVE  a generated summary above the results

WILL        increase search-to-cart rate from 3.0% to 3.3% (+10% rel)

UNIT        user (consistent experience across visits)
ELIGIBLE    assigned only on queries that trigger a summary
DURATION    14 days, whole weeks

PRIMARY     search-to-cart rate
GUARDRAILS  p95 latency, cost per session, return rate, complaints
EXPLORATORY time on page, queries per session, scroll depth

SAMPLE      53,182 per arm for the 10% MDE; available in under a day,
            so duration is set by the weekly cycle, not by power

[+] The decisions that make this a good design

  • Assignment at eligibility. Only queries that actually produce a summary enter the experiment. Assigning all searches would dilute the effect across users who never saw the feature (19.5).
  • Cost as a guardrail, not a footnote. At roughly $0.0005 per summary this is a real operating expense, and a conversion win that costs more than it earns is a loss.
  • Fourteen days, not seven. Long enough to see novelty decay in the daily plot (19.12), and two complete weekly cycles.
  • Model version pinned. Recorded in the brief so the treatment is a fixed thing.

[retail] The result, and what to do with it

day 14text
VALIDITY    split 50.1 / 49.9, chi-square p = 0.42        OK
            daily effect flat after day 3                 OK (novelty decayed)

PRIMARY     search-to-cart   3.00% -> 3.19%
            +0.19pp, +6.3% relative, 95% CI [+1.8%, +10.8%]

GUARDRAILS  p95 latency      284ms -> 291ms               OK
            return rate      unchanged                    OK
            cost/session     $0.0000 -> $0.0004           FLAGGED
            complaints       unchanged                    OK

A real but smaller effect than hypothesised: the interval excludes zero, so it is not noise, and it excludes the +10% target too. The decision is now an economic one rather than a statistical one. At 6 million searches a day, +0.19pp is about 11,340 extra carts daily against $2,400 of inference cost — so the change pays for itself only if an incremental cart is worth more than 21 cents. That is a question for finance, and it is answerable. That arithmetic, not the p-value, is the shipping decision, which is why cost was a guardrail rather than an afterthought.

[!] The follow-up that matters more than the result

The first three days showed +11% before settling at +6.3%. Had this run for a week and been read early, it would have shipped as a 10% win and then quietly underdelivered — the classic novelty pattern. Anyone reviewing this experiment should ask to see the daily curve, not just the summary, because the cumulative number conceals exactly the thing that would have misled you.

19.23 Key takeaways

  1. Randomisation balances confounders you never identified. That is why it is the only design that establishes causation, and why it beats observational analysis on far more data.
  2. "We launched it and revenue went up" is not evidence. Without a simultaneous control, your change is inseparable from the season, the campaign and the competitor.
  3. Most ideas fail. Two thirds to 90% at mature programmes. A team whose experiments nearly always win is measuring something wrong.
  4. Skip the experiment when traffic is too thin, the change is mandatory, or the effect is below your MDE — but never because you are confident.
  5. Randomise on the user by default, and always analyse at the unit you randomised. Counting sessions when you assigned users understates variance.
  6. Assign by salted hash: deterministic, stateless, and independent across concurrent experiments.
  7. Assign at the point of eligibility. Enrolling users who cannot experience the change dilutes a real effect into nothing.
  8. One primary metric, named before launch, plus a small fixed set of guardrails. Everything else is exploratory and cannot justify shipping.
  9. Guardrails catch wins that are losses. Click-through up 6%, revenue flat, returns up 3% is a failure that ships without them.
  10. Validate your proxy metric at least once against the outcome it stands for. Optimising an unvalidated proxy is how teams spend years improving the wrong number.
  11. Write the kill criterion before launch. Agreed in advance it costs nothing; agreed afterwards it is a negotiation you will lose.
  12. Halving the detectable effect quadruples the traffic needed. 10% relative lift needs 53,000 per arm; 5% needs 208,000.
  13. Run whole weeks, and prefer two. A single week cannot distinguish an effect from an unusual week.
  14. Run A/A tests continuously. If more than about 5% come out significant, your variance is understated and every result is overconfident.
  15. Sample ratio mismatch invalidates the experiment. A 51.2/48.8 split looks trivial and is overwhelming evidence of a bug — automate the chi-square check.
  16. Plot the daily effect, not just the cumulative one. Novelty decays, primacy recovers, and a real effect is flat.
  17. Separate "is it broken?" from "is it better?" Watch guardrails continuously, hide the primary metric until the planned end.
  18. Interference breaks the control group. Shared inventory, shared models and network effects all mean the treatment changes the baseline.
  19. Read validity, then guardrails, then the primary metric as an interval, and judge the shipping decision against the lower bound.
  20. Segment findings need pre-registration, a mechanism, and ideally replication. Ten segments give a 40% chance of a story that isn't there.
  21. CUPED removes variance equal to the squared pre-period correlation. At r = 0.76 that was 57.6%, worth 2.36× the sample size.
  22. A flat result is a finding. Record it with its interval, and delete the code.
  23. Interleaving is roughly an order of magnitude more sensitive for ranking comparisons, because each user is their own control. Bandits optimise rather than measure.
  24. Individual wins do not add up; a long-run holdout is the only instrument that shows what a quarter of shipping actually delivered.
  25. For LLM features, pin the model version, make cost a guardrail, and evaluate offline first. An unpinned provider is an uncontrolled variable in your experiment.

[i] Vocabulary check

You should be able to explain: control and treatment, randomisation unit, salted hash assignment, eligibility-based assignment, primary metric, guardrail metric, proxy metric, Goodhart's law, minimum detectable effect, A/A test, sample ratio mismatch, novelty effect, primacy effect, peeking, fixed-horizon versus sequential testing, always-valid inference, interference, cluster randomisation, switchback, interleaving, multi-armed bandit, CUPED, variance reduction, holdout, and pre-registration.

19.24 Interview drills

Experimentation questions test judgement more than arithmetic. The strongest answers name the way the experiment could be lying to you before anyone asks.

1. Why do we need a control group? We can compare before and after.

Because everything else changes at the same time. If revenue rose 4% the week we shipped, that week also had a marketing email, a competitor stock-out and a seasonal shift. Before-and-after can't separate our change from any of it.

A simultaneous control group experiences all those same external factors, so the difference between arms isolates our change. And the reason randomisation specifically matters is that it balances confounders we never thought to measure — device, tenure, market, habit. That's something no statistical adjustment can do, because adjustment only handles variables you identified.

2. The experiment shows +2% on day two. Can we ship?

No, for two separate reasons. First, that's peeking — if we check daily and stop when it looks good, the false positive rate isn't 5%. Simulated against a true null, twenty checks takes it to around 25%.

Second, day two is exactly when novelty inflates results. In a worked example the first three days showed +11% and it settled at +6.3%; shipping early would have promised nearly double what the change delivers. I'd want the planned duration, whole weeks, and the daily effect curve to confirm it's flat. If we genuinely need the option to stop early, that's a decision to make upfront — group sequential boundaries or always-valid intervals — not something to improvise.

3. Control got 51,200 users and treatment 48,800. Is that a problem?

Yes, and I'd stop the analysis. A chi-square test on that split gives p around 3e-14 — it is not chance. It looks like a trivial 2.4% imbalance, which is exactly why this needs to be an automated check rather than something you eyeball.

Sample ratio mismatch invalidates the whole experiment, because whatever is excluding users from one arm almost certainly correlates with behaviour, and it can't be corrected in analysis. I'd check the usual causes in order: does the treatment error out for some users, so they never log exposure; is there a redirect or latency difference; is bot filtering applied after assignment; and are both arms logging exposure at the same point in the flow. That last one is the most common and the most boring.

4. How do you choose the randomisation unit?

User by default, because it gives a consistent experience across visits and devices and matches how people actually perceive the product. Session or request give more power but let the same person see both variants, so they're only safe for changes nobody can notice — a caching or infrastructure test.

The thing I'd flag is that whatever I randomise on, I must analyse on. If I assign by user and then compute significance over sessions, one heavy user's twenty sessions get counted as twenty independent observations, which understates variance and produces confident nonsense. And if there's interference — shared inventory, a shared model retraining on live behaviour — user randomisation breaks down and I'd look at cluster or switchback designs.

5. Click-through is up 6% but revenue is flat. Ship it?

No, and this is exactly why guardrails exist. Click-through is a proxy; revenue is closer to what we actually care about. If the proxy moved and the real metric didn't, the proxy has stopped tracking the thing it was standing in for.

The mechanism is usually explainable — a ranker that learned to promote visually appealing products gets more clicks and more returns. I'd check the return rate and margin specifically. More broadly this is Goodhart's law: optimisation pressure finds the cheapest way to satisfy a measure, which is rarely the thing the measure represented. The lesson I'd take forward is to validate a proxy against the real outcome at least once before building a roadmap on it.

6. We don't have enough traffic to detect a 3% lift. What are the options?

First I'd confirm the arithmetic, because the relationship is inverse-square: halving the effect you want to detect roughly quadruples the traffic needed. So a 3% target is genuinely expensive compared to a 10% one.

Then the options in order of preference. Variance reduction — CUPED using each user's pre-period behaviour — is usually the best value; at a pre/post correlation of 0.76 it removed 57.6% of the variance, worth about 2.4 times the sample size, for the cost of a join. A more sensitive metric closer to the change helps. Testing a bolder variant with a bigger expected effect helps. Interleaving, if it's a ranking comparison. And if none of those work, the honest answer is to ship on judgement with monitoring — but not to run an underpowered test and interpret the result.

7. What is an A/A test and why bother?

Both arms get an identical experience, so the platform should report no difference. It's testing the measurement system rather than a product change.

It catches broken assignment, instrumentation that differs between arms, and pre-existing bias. The most valuable thing it catches is understated variance: if I run many A/A tests, about 5% should come out significant at alpha 0.05, and if 15% do then my analysis is treating correlated observations as independent and every result I've shipped was overconfident. That's why one A/A test proves little — the value is in running them continuously and tracking the false positive rate over months.

8. Explain CUPED to an engineer.

You adjust each user's outcome by their own behaviour before the experiment started. A user who always spends heavily will spend heavily in either arm, so that predictable part is noise as far as measuring the treatment goes — subtract it and what's left is a much tighter estimate.

It's unbiased because pre-period data can't be affected by a treatment that hadn't happened yet. The variance reduction equals the squared correlation between pre and post periods, so r = 0.7 removes about half the variance — in a case I ran, r = 0.76 gave 57.6% reduction, equivalent to 2.36 times the traffic. It doesn't help for new users with no history, and it reduces variance rather than bias, so it can't rescue an experiment with sample ratio mismatch or interference.

9. The result is flat. What do you tell the team?

First, which kind of flat. If the experiment was adequately powered and the interval is tight around zero, that's a real finding: the change doesn't work, and I'd remove the code rather than leave a dormant variant as permanent complexity.

If the interval is wide and includes effects we'd have shipped for, it's inconclusive rather than negative, and the answer is more data or variance reduction. I'd also check for dilution — if only a fraction of assigned users could actually experience the change, a real effect gets averaged away, and re-running with assignment at eligibility often fixes it. The cultural point I'd make is that null results need to be recorded as findings with their intervals. A team that treats them as embarrassing will go looking through segments for something to report, and it will find something.

10. How would you A/B test an LLM-generated summary feature?

Mostly like anything else, with five specific complications. I'd pin the model version for the whole run, because a provider updating the model mid-experiment silently changes the treatment. I'd fix temperature to reduce variance that has nothing to do with my change.

Cost per session becomes a first-class guardrail, not a footnote — a conversion lift that costs more in inference than it earns isn't a win, and that calculation should be in the brief. Quality can't be scored directly, so I'd use behavioural proxies like task completion and escalation rate, backed by offline judging on a sample. And latency is asymmetric, since generation takes seconds, so I'd stream the response and watch time-to-first-token, otherwise I'm testing the feature plus a slowdown. Above all I'd do the offline evaluation first — rejecting a bad prompt in ten minutes beats spending two weeks of traffic on it.

11. Ten experiments each showed +1%. Why isn't the business up 10%?

Because individual wins don't compose. They overlap and interact, some were novelty that decayed after launch, and at a 5% false positive rate some were never real. Each was measured against a control that already contained the previous changes, so the effects aren't independent increments.

The instrument for answering this properly is a long-run holdout — a small slice, typically 1 to 5% of users, kept on the old experience for a quarter. It measures what the accumulated shipping actually delivered together. It's the least popular measurement in any organisation, because it sometimes shows a quarter of work was worth less than the sum of its reported wins, which is exactly why it's worth having.

12. When would you use interleaving or a bandit instead of an A/B test?

Interleaving for ranking comparisons. You blend both rankers' results into one list and see which source gets clicked, so every user is their own control — the same reason a paired test beats an unpaired one. It's roughly an order of magnitude more sensitive, which matters because between-user variance normally swamps ranking differences. The limitation is it tells you which ranking users prefer, not what happens to revenue.

A bandit when I want the best outcome during a short campaign with several options — it shifts traffic toward whatever is winning. But I'd be explicit that it optimises rather than measures: it deliberately starves losing arms, so I end up with a vague estimate for everything except the winner, and the adaptive allocation breaks standard confidence intervals. If I need a reliable effect size for a lasting decision, that's a fixed A/B.

Where this leaves you

Three chapters ago a number was just a number. You can now say how it was sampled, how uncertain it is, whether a model producing it has been evaluated honestly, and whether a change to the system genuinely caused an improvement or merely coincided with one.

That last one is the point of this chapter and, arguably, of the whole track. Every other technique in the course measures what happened. A randomised experiment is the only one that tells you what your work caused — which is the difference between shipping confidently and shipping hopefully.

[+] One chapter still to come

Every distribution used across statistics, machine learning and this chapter — Binomial, Poisson, Normal, and the rest — was used as a tool, not derived. The final chapter opens each one up: full step-by-step derivations of every mean and variance formula, and the mental map for identifying which distribution a new problem actually needs.