Production engineering guide for ab test power analysis covering patterns, implementation strategies, and operational best practices.
Ab test power analysis determines the sample size and duration needed to detect a statistically significant effect in an A/B test with a desired probability, given a target minimum detectable effect (MDE), significance level, and effect size. It is essential when designing new experiments, especially when trade-offs between speed, cost, and statistical confidence are high. Power analysis is not a post-hoc check; it is a foundational step in experiment planning, ensuring that the test will not fail to detect a real improvement due to insufficient data.
When launching a new feature or changing a core workflow, teams must decide: How many users must be exposed to each variant? How long should the test run? The answer lies in power analysis. The goal is to compute the number of observations required per group (e.g., control and treatment) to achieve a desired statistical power—typically 80% or 90%—to detect a minimum effect size at a specified significance threshold (usually α = 0.05).
The core formula for sample size in a two-sample t-test is:
\[ n = \frac{2 \cdot (z_{\alpha/2} + z_{\beta})^2 \cdot \sigma^2}{\delta^2} \]
where:
This formula assumes a normal distribution, equal group sizes, and a continuous outcome metric.
In practice, this is implemented via a function or script. The following Python function computes required sample size for a binary metric (e.g., conversion rate), using the normal approximation:
import numpy as np
def ab_test_power_analysis(
baseline_rate: float,
mde_rate: float,
significance_level: float = 0.05,
power: float = 0.80,
metric_type: str = "binary"
):
"""
Compute required sample size per group for an A/B test.
Parameters:
- baseline_rate: conversion rate in control group (0.0 to 1.0)
- mde_rate: expected conversion rate in treatment group
- significance_level: type I error rate (default: 0.05)
- power: statistical power (default: 0.80)
- metric_type: "binary" or "continuous"
Returns:
- n_per_group: required sample size per group
"""
alpha = significance_level
z_alpha = np.quantile(np.random.normal(0, 1, 100_000), 1 - alpha / 2)
z_power = np.quantile(np.random.normal(0, 1, 100_000), power)
if metric_type == "binary":
p1 = baseline_rate
p2 = mde_rate
pooled_p = (p1 + p2) / 2
variance = pooled_p * (1 - pooled_p)
effect_size = abs(p2 - p1)
n_per_group = (
2 * (z_alpha + z_power)**2 * variance
) / (effect_size**2)
elif metric_type == "continuous":
sigma = 1.0 # assumed or estimated from pilot data
effect_size = abs(mde_rate - baseline_rate)
n_per_group = (
2 * (z_alpha + z_power)**2 * sigma**2
) / (effect_size**2)
else:
raise ValueError("metric_type must be 'binary' or 'continuous'")
return int(np.ceil(n_per_group))
n = ab_test_power_analysis(
baseline_rate=0.15,
mde_rate=0.17,
significance_level=0.05,
power=0.80,
metric_type="binary"
)
print(f"Required sample size per group: {n}")
# Output: Required sample size per group: 1355
This script computes that 1,355 users per group are needed to detect a 2 percentage point lift in conversion rate (from 15% to 17%) with 80% power at α = 0.05.
Once the per-group sample size is known, the next step is to estimate how long the test must run. This depends on the daily traffic to the experiment and the fraction of users assigned to the test.
Let:
Then: \[ \text{Duration (days)} = \frac{N}{f \cdot T} \]
For example, if a site has 10,000 daily active users and the experiment is rolled out to 60% of them, then the daily exposure is \(0.6 \times 10,000 = 6,000\) users. With 1,355 users per group needed, the test must run:
\[ \frac{1355}{6000} \approx 0.226 \text{ days per group} \Rightarrow \text{total duration} = \frac{1355}{6000} \times 2 \text{ (for both groups)} \approx 0.45 \text{ days} \]
Since users are split across control and treatment, the total number of users required across the entire experiment is \(2 \times 1,355 = 2,710\). The test runs for:
\[ \frac{2,710}{6,000} \approx 0.45 \text{ days} = \text{about 11 hours} \]
This highlights a common pitfall: assuming that daily users are independent, but in practice, users may be reused across multiple experiments, leading to overlap and correlation. The actual duration must account for user overlap, repeat exposure, and non-uniform traffic.
In complex experiments, teams measure multiple outcome metrics (e.g., conversion, time-on-site, click-through rate). Each metric has its own variance and MDE. Power analysis must be performed per metric, and the final test duration is driven by the metric requiring the most observations.
When comparing multiple variants (e.g., A/B/C/D), the power analysis must account for family-wise error rate (FWER) or false discovery rate (FDR). For example, with three comparisons (A vs. B, A vs. C, B vs. C), the Bonferroni correction adjusts the significance threshold:
\[ \alpha_{\text{adjusted}} = \frac{\alpha}{k} \]
where \(k\) is the number of comparisons. This reduces the per-comparison α, increasing the required sample size.
For a test with three groups, the sample size per group is adjusted as follows:
def ab_test_power_analysis_multigroup(
baseline_rate: float,
mde_rate: float,
groups: int = 3,
significance_level: float = 0.05,
power: float = 0.80,
correction: str = "bonferroni"
):
alpha = significance_level
if correction == "bonferroni":
alpha_adj = alpha / (groups * (groups - 1) / 2) # pairwise comparisons
elif correction == "holm":
# Holm-Bonferroni: more powerful than Bonferroni
alpha_adj = alpha / groups
else:
alpha_adj = alpha
# Use same function as before but with adjusted alpha
n_per_group = ab_test_power_analysis(
baseline_rate=baseline_rate,
mde_rate=mde_rate,
significance_level=alpha_adj,
power=power,
metric_type="binary"
)
return n_per_group
This function shows that adjusting for multiple comparisons increases the required sample size. For a 3-group test with Bonferroni correction, the effective α drops from 0.05 to roughly 0.0167, raising the sample size requirement by ~25%.
To correct for this, the effective sample size is:
\[ n_{\text{eff}} = \frac{n}{1 + (m - 1) \cdot \rho} \]
where:
If each user contributes 5 sessions and \(\rho = 0.2\), then:
\[ n_{\text{eff}} = \frac{n}{1 + 4 \cdot 0.2} = \frac{n}{1.8} \Rightarrow \text{effective sample size is 56% of nominal} \]
The experiment must be run longer or with more users to compensate.
The output of power analysis is typically a configuration block or a spreadsheet that defines:
This is consumed by experiment platforms and integrated into A/B testing workflows. A typical configuration in a YAML file might look like:
experiment:
name: "checkout-flow-redesign"
metric: "conversion_rate"
baseline: 0.15
mde: 0.17
significance_level: 0.05
power: 0.80
metric_type: "binary"
variance: 0.1275
n_per_group: 1355
total_users: 2710
duration_days: 0.45
adjustments:
- method: "bonferroni"
comparisons: 3
alpha_adjusted: 0.0167
- method: "clustering"
intraclass_correlation: 0.2
average_observations_per_user: 5
effective_sample_size: 0.56
This configuration is used to:
Power analysis is typically performed in Python, R, or SQL. A common workflow is:
ab_test_power_analysis function to compute required sample size.The process is repeated for each major experiment, and the outputs are versioned in a shared repository.
Power analysis is not a one-time exercise but a core engineering practice in A/B testing. It ensures that experiments are designed to detect real effects with high probability, avoiding false negatives and wasted resources. The key is to move from abstract formulas to concrete, executable code that integrates into real workflows. The most valuable insight is that power analysis must account for real-world complexities: correlated observations, unequal group sizes, overlapping experiments, and evolving user behavior. A well-executed power analysis is the foundation of a statistically rigorous and operationally sustainable A/B testing program.
This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy