Confidence intervals show up in A/B test reports, ML evaluation, and survey dashboards, and they are misread constantly. This post covers how a confidence interval for a mean works, when to use z vs t, and how to verify your numbers in code.
I also maintain a free calculator that shows every step, linked at the end.
What a confidence interval for the mean tells you
You have a sample, not the whole population. The sample mean is your best point estimate, but it carries uncertainty. A confidence interval wraps that estimate in a range:
CI = x̄ ± E
where E is the margin of error.
z-interval vs t-interval
Population σ known (or n is large) → z-interval:
CI = x̄ ± z(α/2) · σ/√n
Population σ unknown (the usual case) → t-interval:
CI = x̄ ± t(α/2, n−1) · s/√n
-
x̄= sample mean -
s= sample standard deviation (divide by n−1, not n) -
s/√n= standard error -
n−1= degrees of freedom
The t-distribution has heavier tails, which compensates for estimating σ from a small sample. As n grows, t converges to z.
Common z critical values:
| Confidence | z critical |
|---|---|
| 90% | 1.645 |
| 95% | 1.960 |
| 99% | 2.576 |
Worked example
Resting heart rate (bpm) for 8 adults: 72, 68, 75, 70, 74, 71, 69, 73. We want a 95% CI. σ is unknown, so we use t.
- Mean: 572 / 8 = 71.5
- Sum of squared deviations: 42.00
- Sample SD: √(42 / 7) = √6 = 2.4495
- Standard error: 2.4495 / √8 = 0.8660
- Critical value: t(0.025, df=7) = 2.3646
- Margin of error: 2.3646 × 0.8660 = 2.0477
- Interval: 71.5 ± 2.0477 → (69.4523, 73.5477)
Verify it in Python
import numpy as np
from scipy import stats
data = np.array([72, 68, 75, 70, 74, 71, 69, 73])
mean = data.mean()
sem = stats.sem(data) # uses ddof=1 by default
low, high = stats.t.interval(0.95, df=len(data) - 1, loc=mean, scale=sem)
print(f"95% CI: ({low:.4f}, {high:.4f})")
# 95% CI: (69.4523, 73.5477)
Other tools:
-
Excel:
=CONFIDENCE.T(alpha, standard_dev, size)returns the margin of error -
R:
t.test(x, conf.level = 0.95)$conf.int
What moves the margin of error
- Sample size: E scales with 1/√n, so quadrupling n halves the margin of error.
- Variability: noisier data means a wider interval.
- Confidence level: 99% is wider than 95%. You trade precision for certainty.
Common mistakes
- Using z when σ is unknown. This understates the margin of error, especially for small samples.
- Misreading "95%". It does not mean there's a 95% chance the true mean is inside this specific interval. It means that if you repeated the sampling many times, about 95% of the intervals built this way would contain the true mean.
- Dividing by n instead of n−1 when computing the sample SD.
- Ignoring assumptions. For small n, the data should be roughly normal. If it clearly isn't, consider bootstrapping.
Try it without writing code
I built a free calculator that takes raw data or summary stats, supports z and t intervals at 90/95/99%, and shows the standard error, critical value, margin of error and the full step-by-step LaTeX working:
👉 Confidence Interval Calculator for Mean
Paste in the heart-rate data from the example above and you should get the same interval as the Python snippet.
If you spot a bug or want another test added (t-tests, ANOVA, non-parametric), let me know in the comments.

Top comments (0)