Experimentation & Causal Inference
A/B testing, variance reduction and causal methods when randomization is impossible
A/B testing fundamentals, done rigorously
After this you can design and defend a clean online experiment.
An A/B test is a chain of decisions made before you ever see a number. The interviewer is checking whether you make those decisions in the right order and can defend each one.
At Cursor the unit of analysis is the user-AI interaction, billions per period and the product is non-deterministic. That changes the texture of experiment design but not its skeleton. A rigorous test starts from a sharp hypothesis and walks down to a duration you can name out loud.
- 1Hypothesis. A falsifiable statement with a direction: "capping agent tool-call retries at 3 will cut p95 turn latency without lowering task-completion rate." Vague hypotheses produce vague metrics.
- 2Primary metric + guardrails. One metric you are powered to move (the primary metric), plus 2-3 guardrails you refuse to harm (completion rate, error rate, daily active edits). Add counter-metrics for the obvious gaming risk.
- 3Randomization unit. Pick the level at which exposure is independent. For an editor this is almost always the user, occasionally the workspace, rarely the request.
- 4Power, MDE, sample size. Decide the smallest effect worth detecting (the MDE), then solve for n at your variance and a fixed alpha/beta. The MDE is a product decision, not a statistics one.
- 5Duration. n divided by daily eligible traffic, rounded up past at least one full weekly cycle so weekday/weekend mix and novelty wash out.
Interactive diagram. Tab through its regions; each focused region shows its detail in the panel below.
Every decision is made before you see a number; the sanity gate is where you earn the right to read results.
Randomization unit: why request-level leaksthe most common trap in an editor experiment
Randomizing at the request or keystroke level feels efficient because you get more units. It quietly breaks the experiment. A single developer's session spans thousands of requests and their behavior in one request depends on what the model did in the last one. Split at the request level and the same person experiences both arms, so the arms contaminate each other and your treatment effect collapses toward zero.
- Unit
- User
- When it is right
- Almost always for product changes; behavior carries across a session
- The failure mode
- Fewer units than requests, so you need more calendar time
- Unit
- Workspace / repo
- When it is right
- Team-level features, shared context, seat-based effects
- The failure mode
- Heavy clustering inflates variance if you ignore it
- Unit
- Session
- When it is right
- Short-lived, stateless surfaces with no cross-session memory
- The failure mode
- Leaks if the change has any sticky or learning effect
- Unit
- Request
- When it is right
- Stateless system-level swaps that cannot affect later requests
- The failure mode
- Within-user contamination; rarely valid for product UX
| Unit | When it is right | The failure mode |
|---|---|---|
| User | Almost always for product changes; behavior carries across a session | Fewer units than requests, so you need more calendar time |
| Workspace / repo | Team-level features, shared context, seat-based effects | Heavy clustering inflates variance if you ignore it |
| Session | Short-lived, stateless surfaces with no cross-session memory | Leaks if the change has any sticky or learning effect |
| Request | Stateless system-level swaps that cannot affect later requests | Within-user contamination; rarely valid for product UX |
Default to user-level for product changes; justify anything coarser or finer explicitly.
Peeking and sequential testingthe bug that fakes wins
A fixed-horizon test promises one significance level if you look once, at the end. Engineers in a hurry look every morning and ship the moment p drops below 0.05. That naive repeated peeking can inflate the false-positive rate past 30 percent. You either commit to a fixed sample and look once or you use a method built for continuous monitoring.
Sequential / mSPRT-style inference
Look as often as you want, type-I error still controlled
Pay for it with a wider effective threshold
Pre-planned interim looks with alpha-spending
Can stop early for a clear win or for futility
Needs the look schedule fixed up front
Sanity checks before you read resultstrust nothing until these pass
- Sample-ratio mismatch (SRM). If you assigned 50/50 but observe 50.8/49.2 at billions of units, a chi-square test flags it. SRM means the randomization or logging is broken and the whole readout is suspect.
- A/A test. Run two identical arms before or alongside the real test; a significant "effect" between them exposes a pipeline or variance-estimation bug.
- Instrumentation parity. Confirm both arms log the same events, the same way; a treatment that adds a new code path often adds new logging too, which silently shifts a metric.
When handed a result, don't react to the number first. Say "before I read the effect, did SRM pass and was this an A/A-clean pipeline?" Leading with diagnostics signals you have run real experiments and have been burned by a broken one.
"p < 0.05, ship it" is not an answer. Report the effect size with a confidence interval and ask whether the lower bound clears the practical-significance bar you set. A statistically significant 0.1 percent latency win that costs a week of eng time fails the decision rule you should have pre-registered.
Takeaway. Design top-down: hypothesis to primary metric to user-level randomization to power to duration, then refuse to read results until SRM and an A/A check pass.
Self-check
QYou want to test a change to Cursor's Tab completion model. An engineer proposes randomizing at the individual completion-request level to maximize sample size. What is the core problem and what unit would you use instead?
Variance reduction & sensitivity
After this you can detect smaller effects faster on noisy telemetry.
On reliability data, the bottleneck is rarely the effect being small. It is the variance being enormous. Cutting variance is how you ship a real result in two weeks instead of two months.
Latency and reliability metrics are heavy-tailed: most turns are fast, a few are catastrophic and the slow tail dominates the mean and its variance. Required sample size scales with variance, so a method that shrinks variance directly shrinks the n and the calendar time you need. This is the single most impactful skill on a performance DS team.
CUPED and covariate adjustmentthe workhorse
CUPED (controlled-experiment using pre-experiment data) uses each user's pre-period metric as a covariate to remove the part of their outcome that was predictable before the experiment started. Because the pre-period value is unaffected by treatment, subtracting its influence cannot bias the estimate; it only strips out noise. A user who was already slow stays accounted for, so the residual variance you test on is smaller.
theta = cov(Y, X_pre) / var(X_pre) Y_adj = Y - theta * (X_pre - mean(X_pre)) # variance reduction ~ correlation(Y, X_pre)^2 # a pre-period that correlates 0.7 with the outcome # removes ~49% of the variance -> ~half the required n
- The variance reduction equals the squared correlation between the pre-period covariate and the outcome, so pick a covariate that genuinely predicts the metric.
- Stratification and regression adjustment (CUPAC, ML-predicted controls) generalize the same idea to richer covariates.
- Adjustment never fixes a biased experiment; it makes an unbiased one more sensitive.
Taming heavy tailswhy the mean lies
- Technique
- Winsorize / cap
- What it does
- Clip extreme values to a high percentile before averaging
- The cost
- Hides genuine tail regressions if the cap is too aggressive
- Technique
- Log-transform
- What it does
- Compresses the tail so the mean is stable and tests are sensitive
- The cost
- Effect is now multiplicative; translate back for stakeholders
- Technique
- Percentile / quantile test
- What it does
- Test p95 latency directly, the number users actually feel
- The cost
- Quantile variance is higher; use bootstrap or quantile-regression SEs
| Technique | What it does | The cost |
|---|---|---|
| Winsorize / cap | Clip extreme values to a high percentile before averaging | Hides genuine tail regressions if the cap is too aggressive |
| Log-transform | Compresses the tail so the mean is stable and tests are sensitive | Effect is now multiplicative; translate back for stakeholders |
| Percentile / quantile test | Test p95 latency directly, the number users actually feel | Quantile variance is higher; use bootstrap or quantile-regression SEs |
Match the transform to the question: averages for cost, percentiles for felt experience.
A mean-latency test can read flat while p99 doubles, because the mean is anchored by millions of fast turns. If the reliability complaint is about the tail, power the test on the tail. Saying "I'd test p95 with bootstrapped CIs, not the mean" is the kind of specificity this role screens for.
Triggered analysis and the right estimatorcount the right users, with the right standard errors
- Trigger. Only include users who actually hit the changed code path. If 3 percent of users ever reach the modified agent retry path, diluting the effect across the other 97 percent destroys power. Define the trigger precisely and apply it identically to both arms.
- Ratio metrics. For metrics like success-per-attempt the user is the unit but the metric is a ratio of sums; use the delta method to get correct variance rather than treating it as a simple mean.
- Clustered errors. Session- or request-level rows from the same user are correlated; cluster standard errors at the randomization unit or you will dramatically understate uncertainty.
Powering for a rare failure is brutal. To detect a relative change in a 0.5 percent timeout rate, you may need millions of users. Before accepting that n, ask whether a smarter, denser proxy metric (time-to-first-token, a continuous reliability score) captures the same harm with far less variance. Picking a better metric often beats waiting for a bigger sample.
Takeaway. Shrink variance, don't just grow n: CUPED on a predictive pre-period, tail-aware transforms, triggered analysis and clustered errors turn an underpowered test into a decisive one.
Self-check
QCUPED uses a user's pre-experiment metric as a covariate. Why does this reduce variance without introducing bias and what determines how much variance it removes?
When you can't randomize
After this you can select the right quasi-experimental method per problem.
Most reliability questions are not A/B-able. A model swap, an infra migration or a region-wide incident hits everyone at once. There is no clean control arm to assign, so you reconstruct the counterfactual instead.
Quasi-experimental methods estimate "what would have happened without the change" from observational structure. Each one buys an identifying assumption and the interviewer's real test is whether you name that assumption and how you would falsify it. Match the method to the structure of the problem, not to your favorite tool.
- Method
- Difference-in-differences
- Use when
- A clean comparison group exists (another region, client version) and the change hits one of them
- Identifying assumption
- Parallel trends: groups would have moved together absent treatment
- How to stress-test it
- Plot pre-period trends; run a placebo on a pre-treatment date
- Method
- Synthetic control
- Use when
- No single clean control; build a weighted blend of donors (good for staged rollouts)
- Identifying assumption
- The weighted donors reproduce the treated unit's pre-trend
- How to stress-test it
- Check pre-period fit; placebo-test on untreated donor units
- Method
- Regression discontinuity
- Use when
- A sharp threshold assigns the change (timeout cutoff, rollout-percent boundary)
- Identifying assumption
- Units just above and below the cutoff are comparable
- How to stress-test it
- Look for bunching/manipulation at the cutoff; vary the bandwidth
- Method
- Propensity matching
- Use when
- Observational groups differ on measured confounders
- Identifying assumption
- No unmeasured confounders (ignorability) given the covariates
- How to stress-test it
- Check covariate balance after matching; bound unmeasured bias
- Method
- Instrumental variables
- Use when
- An instrument shifts exposure but not the outcome directly
- Identifying assumption
- Relevance + exclusion: instrument affects outcome only through exposure
- How to stress-test it
- Test instrument strength (F-stat); argue exclusion qualitatively
| Method | Use when | Identifying assumption | How to stress-test it |
|---|---|---|---|
| Difference-in-differences | A clean comparison group exists (another region, client version) and the change hits one of them | Parallel trends: groups would have moved together absent treatment | Plot pre-period trends; run a placebo on a pre-treatment date |
| Synthetic control | No single clean control; build a weighted blend of donors (good for staged rollouts) | The weighted donors reproduce the treated unit's pre-trend | Check pre-period fit; placebo-test on untreated donor units |
| Regression discontinuity | A sharp threshold assigns the change (timeout cutoff, rollout-percent boundary) | Units just above and below the cutoff are comparable | Look for bunching/manipulation at the cutoff; vary the bandwidth |
| Propensity matching | Observational groups differ on measured confounders | No unmeasured confounders (ignorability) given the covariates | Check covariate balance after matching; bound unmeasured bias |
| Instrumental variables | An instrument shifts exposure but not the outcome directly | Relevance + exclusion: instrument affects outcome only through exposure | Test instrument strength (F-stat); argue exclusion qualitatively |
The method is only as good as the assumption you can defend and test.
Diff-in-diff in practicethe default reach for infra changes
You migrate one region's agent backend and leave a comparable region untouched. Diff-in-differences subtracts each region's pre-period level and compares the post-period change between them, which cancels out fixed differences between regions and any shock that hit both equally. The estimate is only causal if the two regions were trending in parallel before the migration.
metric ~ treated + post + (treated * post) + region_FE + day_FE # the coefficient on (treated * post) is the causal estimate # always plot the pre-period trends first: # if they diverge before treatment, parallel-trends fails # and the DiD number is not credible
Treated unit vs a weighted blend of donor units
Weights fit the pre-period trajectory, not just the level
Strong for one treated region in a staged rollout
Causal identification lives in a narrow band around the cutoff
Powerful when a threshold mechanically assigns treatment
Only speaks to units near the boundary, not the whole population
When asked "did this rollout cause the latency drop?", don't jump to a method name. First say what the ideal randomized experiment would have been, then explain why it's impossible here, then pick the quasi-experiment whose assumption is most defensible given the data you have. That ordering shows you reach for randomization first and treat causal inference as the fallback it is.
Propensity scoring and matching only adjust for confounders you measured. They do nothing about unobserved ones and a confident causal claim from matching on three covariates invites a fair takedown. State the assumption (ignorability) plainly and, ideally, bound how strong an unmeasured confounder would have to be to overturn your result.
Takeaway. When you can't randomize, name the structure first, then the method: diff-in-diff for a clean control, synthetic control for staged rollouts, RDD for thresholds and always state the assumption you'd falsify.
Self-check
QCursor migrates the agent backend in one region and you want to know if it changed p95 latency. You plan a difference-in-differences against an untouched region. What single check most determines whether your estimate is causal?
Switchbacks, interleaving and online-eval designs
After this you can pick experiment designs suited to an agent product.
Some changes can't be split per user at all. A shared inference pool, a retrieval index, a global rate limiter: there is one system and everyone uses it. The design has to move to the unit that can actually be isolated.
These are the designs that distinguish a senior experimentation DS from someone who only knows two-arm user tests. Each one exists to handle a structure where the simple split leaks or is infeasible.
Switchback experimentsrandomize time, not users
A switchback flips the whole system between treatment and control on a schedule (every 30 minutes, say) and treats each time bucket as a unit. It is the right call for system-level changes where a user-level split is impossible or would leak through shared infra. Because adjacent buckets are correlated and demand has daily rhythm, you balance the schedule across time-of-day and cluster standard errors at the bucket level.
- Design
- Switchback
- Best for
- System-level changes on shared infra (rate limits, routing, a global model swap)
- Key risk to manage
- Temporal autocorrelation and carryover between adjacent buckets
- Design
- Interleaving
- Best for
- Ranking/retrieval quality, comparing context-retrieval variants
- Key risk to manage
- Only measures relative preference, not absolute metric movement
- Design
- Shadow / canary
- Best for
- Pre-launch evidence before a full experiment
- Key risk to manage
- Causal-ish only; confounded by who/what lands in the canary
| Design | Best for | Key risk to manage |
|---|---|---|
| Switchback | System-level changes on shared infra (rate limits, routing, a global model swap) | Temporal autocorrelation and carryover between adjacent buckets |
| Interleaving | Ranking/retrieval quality, comparing context-retrieval variants | Only measures relative preference, not absolute metric movement |
| Shadow / canary | Pre-launch evidence before a full experiment | Causal-ish only; confounded by who/what lands in the canary |
Pick the design by what can be isolated, then defend the matching risk.
Interleavingtwo retrieval variants on one screen
For ranking and retrieval, interleaving merges results from two variants into a single list and watches which side's items the user actually engages with. The same user judges both, which removes between-user variance and makes it dramatically more sensitive than a split test for comparing, say, two context-retrieval strategies for the agent. It tells you which variant wins, not how much a metric will move in production, so it is a fast screen rather than a launch decision.
Network and spillover effectsthe arms aren't independent
- On shared infra, the treatment arm's load can degrade the control arm (or vice versa), so the control is no longer a clean baseline. This is interference and it biases the estimate.
- The fix is to randomize at the isolating boundary: a separate inference pool, a region, a cluster, a time bucket, whatever cleanly separates one arm's resource contention from the other's.
- If you can't isolate, a switchback at least keeps both states from running simultaneously on the same pool.
Novelty, primacy and pre-launch evidenceshort tests mislead with developer tools
Developers react to anything new, up or down, then settle
A 3-day spike can reverse by week 3
Plan a long-running holdout to read the durable effect
Run the change silently or on 1% before a real test
Catches crashes and gross regressions early
Confounded selection, so treat it as a smoke test not proof
If a prompt says "we changed the shared retrieval index, how would you measure it?", the strong answer names the leak first: a user-level split won't isolate a shared resource, so I'd run a switchback or randomize at the cluster boundary, then cluster SEs at the time bucket. Spotting the interference before proposing a design is what separates the answers.
Don't declare a win on day 3 of a developer-tool experiment. Novelty cuts both ways: a new behavior can spike usage that decays or annoy power users who later adapt. If you can't run long, carve out a small long-term holdout so you can read the steady-state effect after the novelty washes out.
Takeaway. Match design to what's isolable: switchback for shared-infra changes, interleaving for retrieval quality, randomize at the isolating boundary to kill spillover and hold out long enough to beat novelty.
Self-check
QYou want to test a change to a global rate limiter that all Cursor users share. Why is a standard user-level A/B test a poor fit and what design handles it?
Causal reasoning under interrogation
After this you can defend a causal claim against a skeptical interviewer.
The deep-dive round isn't a method quiz. It's a pressure test of whether your causal claim survives someone actively trying to break it. Truth-seeking, the value Cursor screens for, looks like welcoming that pressure.
A strong answer has a shape. You state the counterfactual, name your identifying assumption, list the confounders and where they push, then triangulate across methods. When the evidence is thin, you say so and name the experiment that would settle it.
- 1State the counterfactual. Say out loud what would have happened absent the change. "Latency dropped 12 percent" means nothing until you specify against which baseline.
- 2Name the identifying assumption. Parallel trends, ignorability or the exclusion restriction, depending on the method, plus exactly how you'd test or falsify it.
- 3Enumerate confounders and their direction. List what else changed and for each say whether it inflates or deflates your estimate. Direction of bias is more convincing than a vague hedge.
- 4Triangulate. Show the effect holds across an A/B, a diff-in-diff and a back-of-envelope. Agreement across independent methods is far stronger than one tight p-value.
- 5Bound your confidence. If you can't conclude causally, say it and propose the experiment to run next.
Interactive diagram. Tab through its regions; each focused region shows its detail in the panel below.
The same causal claim, handled two ways - the deep-dive round screens for which shape you reach for.
Correlation, causation, reverse causationname which one you're claiming
- Pattern
- Correlation only
- Example on reliability data
- Heavy users have more errors because they make more requests
- How to tell them apart
- Normalize by exposure; the rate, not the count
- Pattern
- Reverse causation
- Example on reliability data
- "Errors cause churn" vs. churning users were already disengaged
- How to tell them apart
- Check temporal order; does the cause precede the effect?
- Pattern
- Confounded
- Example on reliability data
- A model swap and a traffic spike landed the same day
- How to tell them apart
- Hold the confounder fixed (segment, control or instrument)
| Pattern | Example on reliability data | How to tell them apart |
|---|---|---|
| Correlation only | Heavy users have more errors because they make more requests | Normalize by exposure; the rate, not the count |
| Reverse causation | "Errors cause churn" vs. churning users were already disengaged | Check temporal order; does the cause precede the effect? |
| Confounded | A model swap and a traffic spike landed the same day | Hold the confounder fixed (segment, control or instrument) |
Most bad causal claims are one of these three wearing a confident voice.
We see the error rate fell after the rollout, but two other things changed that week, so I can't call it causal yet. A diff-in-diff against the un-migrated region agrees with the raw drop and a back-of-envelope on the retry change predicts the same magnitude. Those three lining up makes me fairly confident and a clean switchback next week would settle it.
One method gives you one set of assumptions to attack. Three independent methods that agree force a skeptic to explain why all three would be wrong in the same direction, which is a much harder argument to make. Reaching for triangulation unprompted is a senior tell.
Don't defend a shaky claim to the death. The fastest way to fail the truth-seeking bar is to keep insisting on causality after the interviewer surfaces a confounder you can't rule out. Conceding "you're right, that confound means I can only claim association here and here's the experiment that would make it causal" scores higher than stubborn confidence.
Takeaway. Defend a causal claim by stating the counterfactual and identifying assumption, naming confounders with their direction and triangulating across methods, then admit uncertainty and name the experiment that would resolve it.
Self-check
QAn interviewer says: "Agent error rate dropped 15 percent the week we shipped a new retry policy. So the retry policy caused it, right?" What is the strongest response structure?