Reach Significance Faster: CUPED Variance Reduction
TL;DR
- →Understand how each user's pre-experiment behavior strips out predictable noise and shrinks the variance of your treatment estimate.
- →See when CUPED helps and when it does nothing: numeric metrics with correlated history win; conversion metrics and new users do not.
- →Learn how Optimizely's regression-based CUPED differs from the 2013 method and how to enable it without biasing results.
Experiments stall for one boring reason more than any other: not enough signal relative to noise. When a metric like revenue-per-visitor swings wildly from user to user, the treatment effect you care about is buried under variance that has nothing to do with your change. CUPED (Controlled-experiment Using Pre-Experiment Data) attacks that variance directly — it uses each user's own pre-experiment behavior to subtract out the predictable part of the noise, so the same experiment reaches significance with less traffic and less time. This article explains the intuition, the math, how Optimizely's regression-based implementation differs from the textbook version, and — just as important — the cases where CUPED does nothing at all.
The Problem: Variance, Not Effect Size, Is Usually the Bottleneck
The number of visitors an experiment needs to detect an effect scales with the variance of the metric divided by the square of the effect you want to detect. You cannot manufacture a bigger effect — that is set by the quality of your idea. But you can often shrink the variance, and every reduction in variance translates directly into a smaller required sample size.
This matters most for exactly the teams that struggle: low-traffic sites, high-value pages that convert rarely, and revenue or engagement metrics with long, heavy tails where a handful of whales dominate the average. For those metrics, the classic advice — "run it longer" — is expensive and sometimes impossible. Variance reduction is the alternative: change the statistics, not the traffic.
CUPED is the most widely adopted variance-reduction technique in the industry, introduced by Deng, Xu, Kohavi, and Walker at Microsoft in 2013 and now standard at companies including Netflix, Booking.com, and Optimizely. If you want the broader context on how experiments reach significance in the first place, see OptiPilot's explainer on how the Optimizely Stats Engine works.
The Intuition: Noise You Can Predict, You Can Remove
Here is the core idea in one sentence: if you can predict part of a user's post-experiment metric before the experiment even starts, that predictable part is not evidence about your treatment — so remove it.
Consider revenue-per-user on an e-commerce site. A customer who spent 500 USD in the two weeks before your test is likely to spend more than average during the test, regardless of which variation they land in. That tendency is a property of the user, not your change. When a big spender happens to land in the treatment group, they inflate the treatment average — pure luck of the draw, and exactly the kind of noise that makes results wobble.
Because assignment to control or treatment is random, a user's pre-experiment behavior is (in expectation) balanced across variations and uncorrelated with the treatment assignment. That is what makes it a safe covariate: it is strongly correlated with the outcome, but independent of the thing you are trying to measure. CUPED subtracts the predictable, user-level component of the metric and analyzes what remains. The treatment effect survives untouched; the random noise shrinks.
The strength of the effect hinges entirely on one quantity: the correlation between a user's pre-experiment metric and their in-experiment metric. High correlation (a user's past revenue strongly predicts their future revenue) means large variance reduction. Zero correlation means CUPED does nothing — it cannot hurt you, but it cannot help either.
flowchart LR
A[Pre-experiment window<br/>e.g. 2 weeks of history] --> B[Per-user covariate X<br/>past value of the metric]
C[In-experiment metric Y] --> D[Regress Y on X<br/>remove predictable part]
B --> D
D --> E[Residuals:<br/>variance reduced]
E --> F[Stats Engine]
F --> G[Significance reached<br/>with less traffic / time]The Math
The classic CUPED estimator (2013)
The original method adjusts the metric mean using a single covariate X (typically the same metric measured in the pre-period). The CUPED-adjusted estimate of the mean is:
Ŷ_cuped = Ȳ − θ (X̄ − E[X])
where θ is chosen to minimize the variance of the adjusted estimator:
θ = Cov(Y, X) / Var(X)
E[X] is the expected value of the covariate across the whole population (the same for both groups, since assignment is random). Subtracting θ(X̄ − E[X]) removes the component of each group's mean that is explained by the covariate. The variance of the adjusted metric is:
Var(Ŷ_cuped) = Var(Ȳ) · (1 − ρ²)
where ρ is the correlation between Y and X. This single equation is the whole story: a covariate correlated 0.7 with the outcome removes 0.7² ≈ 49% of the variance; a covariate correlated 0.3 removes only about 9%. Your variance reduction is the square of the correlation.
Illustrative Python: computing the classic CUPED adjustment
The snippet below computes the classic mean-adjustment CUPED estimate from pre-period and in-experiment arrays. It is intended to build intuition — it is not how Optimizely computes CUPED internally (see the next section), and it uses the pooled covariate to estimate θ, which is the standard practice.
import numpy as np
def cuped_adjust(pre, post):
"""
Classic CUPED (Deng et al. 2013) mean adjustment.
pre : per-user metric value in the pre-experiment window (covariate X)
post : per-user metric value during the experiment (outcome Y)
Returns the CUPED-adjusted post values, aligned per user.
"""
pre = np.asarray(pre, dtype=float)
post = np.asarray(post, dtype=float)
# theta = Cov(Y, X) / Var(X), estimated on the pooled sample
theta = np.cov(post, pre, bias=True)[0, 1] / np.var(pre)
# Center the covariate on its population mean, then subtract its
# predictable contribution from each user's outcome.
adjusted = post - theta * (pre - pre.mean())
return adjusted, theta
# --- demo: correlated pre/post, random assignment ---
rng = np.random.default_rng(0)
n = 20_000
user_baseline = rng.gamma(shape=2.0, scale=10.0, size=n) # heavy-tailed "spend"
post = user_baseline + rng.normal(0, 5, size=n) # in-experiment metric
treatment = rng.integers(0, 2, size=n)
post = post + treatment * 1.0 # true +1.0 effect
adjusted, theta = cuped_adjust(user_baseline, post)
raw_effect = post[treatment == 1].mean() - post[treatment == 0].mean()
adj_effect = adjusted[treatment == 1].mean() - adjusted[treatment == 0].mean()
print(f"theta = {theta:.3f}")
print(f"raw variance of post: {post.var():.2f}")
print(f"cuped variance of adjusted: {adjusted.var():.2f}")
print(f"raw effect: {raw_effect:+.3f}")
print(f"cuped effect:{adj_effect:+.3f}") # unbiased: still ~+1.0, lower variance
The adjusted metric has substantially lower variance while the estimated treatment effect stays centered on the truth. That is the entire value proposition: same answer, tighter confidence interval, fewer users required.
Optimizely's Regression-Based CUPED
Optimizely does not use the 2013 mean-adjustment formula directly. Its implementation is a regression-based covariance adjustment, which the documentation describes as "more advanced than the standard CUPED method." Instead of adjusting the mean with a single θ, Optimizely fits a linear regression:
Yᵢ = α + β·Tᵢ + γᵀ·X̃ᵢ + eᵢ
Yᵢ— the numeric target metric for user iTᵢ— treatment indicator (0 = control, 1 = treatment)X̃ᵢ— centered covariates derived from that user's pre-experiment dataα,β,γ— regression coefficientseᵢ— error term
The coefficient β is the treatment effect, and its Ordinary Least Squares estimate β̂ is an unbiased estimator of the true effect. Including the pre-experiment covariates X̃ᵢ reduces the variance of β̂ — that is the variance reduction, expressed in regression form.
In practice Optimizely runs this in two steps, following the Frisch–Waugh–Lovell theorem:
Regress
Yᵢon the covariatesX̃ᵢand compute each user's predicted value.Compute the residuals
Yᵢ − Ŷᵢand feed those residuals into the Stats Engine.
The Stats Engine then evaluates significance on the residualized, lower-variance metric.
Why regression rather than the classic formula?
The regression framing is strictly more general. As Optimizely's own documentation notes, if the covariate vector X̃ᵢ contained nothing but the single historical value of Yᵢ, the regression method would collapse exactly to the classic CUPED estimator. Because it can incorporate multiple covariates and handles them in a unified least-squares framework, it dominates the standard method — hence Optimizely's choice. In the current release, the covariates are limited to the pre-experiment calculations of your primary and secondary target metrics; user-defined covariates are not yet supported.
When CUPED Applies — and When It Does Not
CUPED is not a universal switch. Verified against Optimizely's documentation, here is where it helps and where it is silent or unavailable.
CUPED helps when
The metric is numeric (revenue per visitor, pages per session, time on site, order value).
Users have pre-experiment history for that metric.
That history is correlated with in-experiment behavior — the stronger the correlation, the larger the reduction.
CUPED does nothing (or is unavailable) when
The metric is a conversion / binary metric. Optimizely restricts CUPED to numeric metrics; it is most effective there and is not offered for conversion-rate metrics.
Users have no prior data. The pre-experiment window runs from (by default) two weeks before the experiment start date up to each user's first decision event. A user with no activity in that window — a brand-new user, or a newly created metric with no history — contributes no covariate, so CUPED has no effect for them.
The pre-period metric is uncorrelated with the outcome. Zero correlation means zero variance reduction. It will not bias your result, but it will not speed it up.
flowchart TD
A[Want faster significance?] --> B{Is the metric numeric?}
B -- No, it is a conversion metric --> X[CUPED not available<br/>consider outlier management instead]
B -- Yes --> C{Do users have<br/>pre-experiment history?}
C -- No, new users / new metric --> Y[CUPED has no effect]
C -- Yes --> D{Is history correlated<br/>with in-experiment metric?}
D -- Weakly / not at all --> Z[Little to no benefit<br/>but safe to leave on]
D -- Strongly --> W[Enable CUPED<br/>meaningful variance reduction]A useful mental model: CUPED rewards metrics where the past predicts the present. Returning-user revenue and engagement metrics are ideal. First-touch acquisition metrics, or anything measured on users with no history, are exactly the cases where it quietly does nothing.
Setting Up CUPED in Optimizely
CUPED lives on Optimizely's new A/B Results page and its warehouse-native Analytics Scorecards. A few prerequisites and steps, verified against the docs:
Prerequisites
Access via Opti ID, on the new A/B Results page.
Manual A/B experiments only. Stats Accelerator experiments and multivariate tests are not supported.
A supported data warehouse: Snowflake, BigQuery, or Databricks (CUPED is part of Warehouse-Native Experimentation Analytics). Contact Optimizely for other warehouses.
Numeric metrics — CUPED options appear only for numeric metrics, alongside outlier management.
Steps
Create an Experiment Scorecard in Analytics (or open the new A/B Results page for your experiment).
Under Advanced options, enable the CUPED toggle.
Optionally set the CUPED duration — the length of the pre-experiment window. The default is two weeks of history; adjust it if a different window better reflects your users' behavior.
Click Run (or Ctrl/Cmd+Enter) to recalculate results with CUPED applied. You can toggle it on and off to compare scorecards side by side.
Because toggling CUPED re-runs the analysis over your warehouse data, treat it as a lens on the same experiment rather than a change to the experiment itself — the underlying decision and conversion events are untouched.
Common Pitfalls
Turning it on and expecting magic on conversion metrics. CUPED is numeric-only. If your primary metric is a conversion rate, CUPED will not appear or apply. For heavy-tailed numeric metrics, pair CUPED with Optimizely's outlier management (Winsorization or capping) — they address different problems (systematic vs. extreme-value variance) and compose well.
Expecting help where there is no history. For a metric that only starts collecting data at experiment launch, or an audience of brand-new users, the covariate is empty and CUPED has no effect. This is not a bug; it is the method's precondition. Plan a longer pre-period, or accept that variance reduction is not available for that metric.
Peeking at "with vs. without CUPED" and cherry-picking. CUPED is a legitimate variance-reduction method configured before you look at results, not a knob to flip after the fact until significance appears. Decide up front that you will report the CUPED-adjusted scorecard, and stick with it. Toggling repeatedly and keeping whichever view crosses the threshold reintroduces exactly the false-positive risk that a disciplined stats process exists to prevent. Continuous monitoring is valid under Optimizely's Sequential Stats Engine in a way it is not under a fixed-horizon test — but that is a property of the statistical method, not a license to cherry-pick which CUPED view you report.
Assuming a covariate-imbalance safety net exists. Optimizely notes there are no automated health checks yet for covariate imbalance, the way there are for Sample Ratio Mismatch. With sparse prior data, an unbalanced covariate can in principle increase variance. In practice this is uncommon, but do not assume the platform will flag it for you.
How Much Time or Traffic Does It Actually Save?
The honest answer: it depends entirely on the correlation between your pre-period and in-experiment metrics, and Optimizely does not publish a guaranteed number. The mechanism, however, is precise. Because variance reduction equals the square of that correlation, and required sample size scales linearly with variance, the fractional reduction in required traffic is approximately the fractional reduction in variance.
The original 2013 CUPED research and subsequent industry practice commonly report variance reductions in the 30–50% range on well-correlated engagement and revenue metrics — which, taken at face value, corresponds to needing roughly 30–50% fewer users (or days) to reach the same significance. Treat that as an illustrative band from the literature, not an Optimizely commitment: on a metric with weak pre-period correlation you might see a few percent, and on a conversion metric you will see nothing because CUPED does not apply.
The practical takeaway for planning: if your metric is numeric and your users have predictive history, CUPED can meaningfully shorten test duration for free — no extra traffic, no change to your experiment design. To translate an expected variance reduction into concrete sample-size and runtime numbers for your own baseline and MDE, use OptiPilot's traffic and sample-size estimator and compare the required duration with and without the reduction.
Summary
CUPED reduces the variance of numeric experiment metrics by using each user's pre-experiment behavior as a covariate, stripping out predictable noise so tests reach significance faster without more traffic. Optimizely implements it as a regression-based covariance adjustment — a generalization of the classic 2013 estimator — that residualizes the metric and feeds the result to its Stats Engine. It shines on numeric, high-history, well-correlated metrics; it is unavailable for conversion metrics and inert for users with no prior data. Turn it on for the right metrics, decide to use it before you look at results, and it is one of the cheapest ways to make an under-powered experiment program faster.