Central Limit Theorem: Formula and Examples

The Central Limit Theorem (CLT) is the bedrock of modern statistics. It states that when you repeatedly sample from any population and calculate the mean of each sample, those sample means will form...

Key Insights

  • The Central Limit Theorem guarantees that sample means follow a normal distribution regardless of the population’s shape, enabling powerful statistical inference with just the standard error formula σ/√n
  • You need surprisingly small sample sizes (often n ≥ 30) for CLT to work effectively, even with highly skewed populations like exponential or uniform distributions
  • CLT breaks down with extreme outliers, heavy-tailed distributions, or when sampling isn’t independent—always verify assumptions before applying confidence intervals or hypothesis tests

Understanding the Central Limit Theorem

The Central Limit Theorem (CLT) is the bedrock of modern statistics. It states that when you repeatedly sample from any population and calculate the mean of each sample, those sample means will form a normal distribution—regardless of whether your original population is normal, uniform, exponential, or bizarrely shaped. This remarkable property lets us make probabilistic statements about populations using normal distribution theory, even when we know nothing about the population’s actual distribution.

Pierre-Simon Laplace formalized this theorem in 1810, though Abraham de Moivre had discovered special cases decades earlier. The CLT explains why normal distributions appear everywhere in nature: most observable phenomena result from averaging multiple random factors, and CLT guarantees this averaging process produces normality.

The Mathematical Formula

The formal statement of CLT says that for independent, identically distributed random variables X₁, X₂, …, Xₙ with mean μ and finite variance σ², the standardized sample mean converges to a standard normal distribution as n approaches infinity:

Z = (X̄ - μ) / (σ/√n) → N(0, 1)

Breaking down each component:

  • : The sample mean, calculated as (X₁ + X₂ + … + Xₙ) / n
  • μ: The true population mean
  • σ: The population standard deviation
  • n: The sample size
  • σ/√n: The standard error of the mean, representing how much sample means vary

The crucial insight is that σ/√n term. As sample size increases, the standard error decreases proportionally to the square root of n. Doubling your sample size doesn’t halve your uncertainty—it reduces it by 1/√2 ≈ 0.707. To halve uncertainty, you need four times the data.

Key Conditions and Assumptions

CLT isn’t magic—it requires specific conditions:

Independence: Each sample must be independent. Sampling without replacement from finite populations can violate this, though the rule of thumb is that if your sample is less than 10% of the population, you’re fine.

Sample Size: The magic number n ≥ 30 appears everywhere in statistics textbooks. This is a rough guideline. For symmetric distributions, n = 10 might suffice. For highly skewed distributions, you might need n > 100. The more non-normal your population, the larger n must be.

Finite Variance: The population must have finite variance. Cauchy distributions and other pathological cases with infinite variance break CLT entirely.

Practical Example: Dice Rolling Simulation

Let’s demonstrate CLT with dice rolls. A single die has a discrete uniform distribution (each outcome 1-6 equally likely), definitely not normal. Watch what happens when we average multiple dice:

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

# Set random seed for reproducibility
np.random.seed(42)

# Simulate rolling dice
def simulate_dice_means(n_dice, n_samples=10000):
    """Roll n_dice dice n_samples times, return mean of each sample"""
    rolls = np.random.randint(1, 7, size=(n_samples, n_dice))
    return np.mean(rolls, axis=1)

# Generate sampling distributions for different sample sizes
sample_sizes = [1, 2, 5, 30]
fig, axes = plt.subplots(2, 2, figsize=(12, 10))

for idx, n in enumerate(sample_sizes):
    ax = axes[idx // 2, idx % 2]
    means = simulate_dice_means(n)
    
    # Plot histogram
    ax.hist(means, bins=50, density=True, alpha=0.7, edgecolor='black')
    
    # Overlay theoretical normal distribution
    mu = 3.5  # Expected value of a die
    sigma = np.sqrt(35/12)  # Variance of uniform distribution on {1,2,3,4,5,6}
    standard_error = sigma / np.sqrt(n)
    
    x = np.linspace(means.min(), means.max(), 100)
    ax.plot(x, stats.norm.pdf(x, mu, standard_error), 
            'r-', linewidth=2, label='Theoretical Normal')
    
    ax.set_title(f'Sample Size n = {n}')
    ax.set_xlabel('Sample Mean')
    ax.set_ylabel('Density')
    ax.legend()

plt.tight_layout()
plt.savefig('clt_dice_demonstration.png', dpi=300)
plt.show()

Even with just n=5 dice, the distribution of means looks remarkably normal. By n=30, it’s nearly indistinguishable from the theoretical normal curve. This is CLT in action.

Real-World Application: Website Load Times

Server response times typically follow right-skewed distributions—most requests are fast, but occasional slow queries create a long tail. Let’s analyze this with CLT:

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

# Generate skewed load time data (exponential distribution)
np.random.seed(123)
population_mean = 250  # milliseconds
population = np.random.exponential(scale=population_mean, size=100000)
population_std = np.std(population, ddof=1)

print(f"Population Mean: {population_mean:.2f} ms")
print(f"Population Std: {population_std:.2f} ms")

# Sample from this population
def calculate_confidence_interval(sample_means, confidence=0.95):
    """Calculate CI using CLT"""
    mean = np.mean(sample_means)
    std_error = np.std(sample_means, ddof=1)
    z_score = stats.norm.ppf((1 + confidence) / 2)
    margin = z_score * std_error
    return mean - margin, mean + margin

# Take multiple samples of size n
n = 50
n_samples = 1000
sample_means = []

for _ in range(n_samples):
    sample = np.random.choice(population, size=n, replace=False)
    sample_means.append(np.mean(sample))

sample_means = np.array(sample_means)

# Calculate confidence interval
ci_lower, ci_upper = calculate_confidence_interval(sample_means)

# Theoretical prediction using CLT
theoretical_std_error = population_std / np.sqrt(n)
theoretical_ci_lower = population_mean - 1.96 * theoretical_std_error
theoretical_ci_upper = population_mean + 1.96 * theoretical_std_error

print(f"\nEmpirical 95% CI: [{ci_lower:.2f}, {ci_upper:.2f}]")
print(f"Theoretical 95% CI: [{theoretical_ci_lower:.2f}, {theoretical_ci_upper:.2f}]")

# Visualize
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))

# Original population (skewed)
ax1.hist(population[:5000], bins=50, density=True, alpha=0.7, edgecolor='black')
ax1.set_title('Population Distribution (Exponential)')
ax1.set_xlabel('Load Time (ms)')
ax1.set_ylabel('Density')
ax1.axvline(population_mean, color='r', linestyle='--', label=f'Mean = {population_mean:.0f}')
ax1.legend()

# Sampling distribution (normal)
ax2.hist(sample_means, bins=50, density=True, alpha=0.7, edgecolor='black')
ax2.axvline(ci_lower, color='g', linestyle='--', label='Empirical 95% CI')
ax2.axvline(ci_upper, color='g', linestyle='--')
ax2.axvline(theoretical_ci_lower, color='r', linestyle=':', linewidth=2, label='Theoretical 95% CI')
ax2.axvline(theoretical_ci_upper, color='r', linestyle=':', linewidth=2)
ax2.set_title(f'Sampling Distribution of Means (n={n})')
ax2.set_xlabel('Sample Mean (ms)')
ax2.set_ylabel('Density')
ax2.legend()

plt.tight_layout()
plt.savefig('clt_load_times.png', dpi=300)
plt.show()

The empirical and theoretical confidence intervals match closely, validating CLT’s predictions. This lets you make statements like “We’re 95% confident the true average load time is between 240 and 260 ms” based purely on sample data.

Visualization and Verification

To truly verify normality, use Q-Q plots (quantile-quantile plots). These plot your data’s quantiles against theoretical normal quantiles. If points fall on a straight line, your data is approximately normal:

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

# Generate sample means from different sample sizes
np.random.seed(456)
population = np.random.exponential(scale=50, size=100000)

fig, axes = plt.subplots(2, 3, figsize=(15, 10))

sample_sizes = [5, 10, 30, 50, 100, 500]

for idx, n in enumerate(sample_sizes):
    ax = axes[idx // 3, idx % 3]
    
    # Generate sampling distribution
    sample_means = [np.mean(np.random.choice(population, n)) for _ in range(1000)]
    
    # Create Q-Q plot
    stats.probplot(sample_means, dist="norm", plot=ax)
    ax.set_title(f'Q-Q Plot: n = {n}')
    ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('clt_qq_plots.png', dpi=300)
plt.show()

For small sample sizes (n=5), you’ll see deviations from the straight line, especially in the tails. As n increases, the points hug the line more closely, confirming convergence to normality.

Common Pitfalls and Best Practices

Don’t blindly apply n ≥ 30: This rule assumes moderate skewness. For heavily skewed data (like income distributions or rare events), you need larger samples. Always visualize your sampling distribution.

Watch for outliers: A single extreme outlier can dominate your sample mean. If you have heavy-tailed distributions (like Pareto or log-normal with high variance), CLT converges slowly. Consider robust statistics or trimmed means instead.

Check independence: Time series data, clustered samples, or sequential measurements often violate independence. Use appropriate methods like time series analysis or mixed-effects models instead.

Small samples from normal populations: If your population is already normal, you don’t need CLT—the sample mean follows a t-distribution exactly. Use t-tests instead of z-tests for small samples.

Practical sample size guidance: Start with n=30 as baseline. If your data is symmetric, you can go smaller. If it’s skewed, go larger. Always validate with Q-Q plots or Shapiro-Wilk tests. When in doubt, bootstrap your confidence intervals—it’s more robust and doesn’t assume normality.

The Central Limit Theorem transforms statistics from theoretical mathematics into practical decision-making. Master it, understand its limitations, and you’ll have a powerful tool for making sense of uncertain data.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.