OS-008 · Probability

Birthday Paradox Simulation

In a group of 23 people, the chance that at least two share a birthday is just over 50%. It sounds too high because we think about matching our own birthday. The question is about any pair, and 23 people form 253 pairs.

Interactive model

OS-008 · birthday-paradox · seeded
exact probabilitysimulated ± 1.96 SE50%
one birthdayshared birthday
Parameters
Each trial draws a fresh group
All days equally likely; 365 ignores February 29
Same seed, same run — here, in simulation.js and in simulation.py.
Groups simulated
10,000
Groups with a shared birthday
5,145
Simulated probability
51.45%
Exact probability
50.73%
Standard error
0.50%
Matching pairs per group
0.701
Expected pairs C(g,2)/d
0.693
Smallest group with P ≥ 50%
23

Example run: default parameters, seed 42.

Why 23 people are enough

Any two people share a birthday with probability 1/365. With 23 people there are 23 · 22 / 2 = 253 pairs, so the expected number of matching pairs is 253/365 ≈ 0.69. The pairs are not quite independent, but treating them as if they were gives a good approximation:

P(shared) ≈ 1 − exp(−g(g − 1) / (2d))

For 23 people and 365 days the exponent is −0.693 = −ln 2, which puts the probability at one half. Solving for even odds gives g ≈ √(2 ln 2 · d) ≈ 1.18√d, about 22.5 for a 365-day year. The number of pairs grows with the square of the group size, and that is the whole paradox.

Compare the question people usually answer in their heads: the chance that one of 22 others shares your birthday is 1 − (364/365)²² ≈ 5.9%. For even odds on that, you need 253 other people.

The exact probability

Seat people one at a time. The second avoids the first birthday with probability (d − 1)/d, the third avoids both with (d − 2)/d, and so on:

P(shared) = 1 − ∏i=0…g−1 (d − i) / d

With 365 days: 30 people give 70.6%, 50 give 97.0%, 57 give 99.0% and 70 give 99.9%. When the group is larger than the number of days, a match is certain. The curve in the lab is this formula; the orange point is the simulation, with a whisker of ±1.96 standard errors.

The same arithmetic governs collisions among random identifiers and hash values. With d equally likely values, a repeat becomes more likely than not after about 1.18√d draws — the “birthday bound” behind birthday attacks on hash functions.

How the simulation works

  1. Draw g birthdays independently and uniformly from d days.
  2. Count matching pairs: each new birthday adds the number of people already on that day. A per-day stamp records which trial last used the day, so nothing needs clearing between groups.
  3. A group with at least one pair counts as a success. The estimate is the fraction of successes; its standard error is √(p̂(1 − p̂)/T) for T groups.

Step draws one group at a time so you can see what groups of 23 look like on the calendar grid: about half have no shared day at all, and most of the rest have a single matching pair.

Example result

With seed 42, 5,145 of 10,000 simulated groups of 23 contained a shared birthday: an estimate of 51.45% against the exact 50.73%, with a standard error of 0.50%. The groups averaged 0.701 matching pairs; the expected number is 0.693.

Output of birthday-paradox with group=23 trials=10000 days=365 seed=42
Groups simulated10,000
Groups with a shared birthday5,145
Simulated probability51.45%
Exact probability50.73%
Standard error0.50%
Matching pairs per group0.701
Expected pairs C(g,2)/d0.693
Smallest group with P ≥ 50%23

Implementation

The page runs simulation.js from the package below; the Python file is a line-by-line port with the same random-number generator, so a given seed produces the same result in both languages. The excerpts show the core of each.

JavaScript — simulation.js
// Exact probability that at least two of g people share a birthday when each of d
// days is equally likely: one minus the chance that all g birthdays differ.
export function exactProbability(g, d) {
  let allDifferent = 1;
  for (let i = 0; i < g; i++) allDifferent *= (d - i) / d;
  return 1 - allDifferent;
}

// One simulated group. stamp[] marks the days already used in this trial, so the
// arrays never need clearing; pairs counts every matching pair of people.
export function trial(s, rng) {
  let pairs = 0;
  for (let j = 0; j < s.group; j++) {
    const day = rng.int(s.days);
    s.birthdays[j] = day;
    if (s.stamp[day] !== s.t) { s.stamp[day] = s.t; s.count[day] = 0; }
    pairs += s.count[day];
    s.count[day]++;
  }
  s.t++;
  if (pairs > 0) s.hits++;
  s.pairs += pairs;
  s.lastPairs = pairs;
  return pairs > 0;
}
Python — simulation.py
def exact_probability(g, d):
    """P(at least two of g people share a birthday), d equally likely days."""
    all_different = 1
    for i in range(g):
        all_different *= (d - i) / d
    return 1 - all_different


def simulate_group(g, d, rng):
    """Draw g birthdays; return the number of matching pairs of people."""
    seen = {}
    pairs = 0
    for _ in range(g):
        day = rng.int(d)
        pairs += seen.get(day, 0)
        seen[day] = seen.get(day, 0) + 1
    return pairs

Run the full model from a terminal:

Shell
node simulation.js --group 23 --trials 10000 --seed 42
python simulation.py --group 23 --trials 10000 --seed 42

Download the model

birthday-paradox.zip6 files · 11.1 KBDownload .zip
Files in birthday-paradox.zip
FileContentsSize
README.mdHow to run it, parameters, example result2.2 KB
model.jsonParameters with ranges and defaults, outputs, the seed-42 example2.1 KB
simulation.jsThe model used on this page plus a command-line runner (Node 18+)7.5 KB
simulation.pyPython 3.9+ port, standard library only5.9 KB
example.csvResult table from the example run6.1 KB
LICENSE.txtMIT License1.0 KB

Assumptions & limitations

  • Every day equally likely. Real birth dates are not uniform: births vary by season and are fewer on weekends and holidays, when fewer deliveries are scheduled. Any departure from uniform makes a shared birthday more likely, so the uniform model gives a lower bound on the real probability.
  • No February 29. The default year has 365 days; the days parameter lets you change the count, not the calendar.
  • Independent people. Twins and other linked birthdays are left out; they also raise the chance of a match.
  • Sampling error. The simulated estimate differs from the exact value by an amount on the order of its standard error; more groups shrink it like 1/√T.

References

  1. Grinstead, C. M. & Snell, J. L. (1997). Introduction to Probability, 2nd ed. American Mathematical Society. math.dartmouth.edu/~prob/prob/prob.pdf
  2. Feller, W. (1968). An Introduction to Probability Theory and Its Applications, Vol. 1, 3rd ed. Wiley.