The birthday paradox: 23 people, one surprisingly likely collision
Calculate birthday collision probabilities in an editable Java notebook and compare any matching pair with a match to your own birthday.
Run cells from top to bottom. Typed declarations and imports carry into later cells. Shift + Enter runs a cell. Reloading or leaving this page discards edits and Java state.
In a room of 23 people, a shared birthday is slightly more likely than no shared birthday. Put yourself in that room and ask whether somebody shares your birthday, though, and the chance is only about six percent. Same room, different question.
The gap is the interesting part of the birthday paradox. We will calculate both answers, change the size of the year, and find the first group where a match becomes more likely than not. No random simulation is needed: we can calculate the probability directly.
Count the outcome that is easier
Assume each birthday is independently selected from 365 equally likely days. Ignore leap days. This is a mathematical model, rather than a claim about the distribution of actual birthdays. Family relationships, seasonal birth patterns, and how the group was selected can all matter outside the model.
The first person can have any birthday. For everyone to have distinct birthdays, the second must avoid one day: probability 364/365. Conditional on those two being different, the third must avoid two days: 363/365. Continue this argument as each person arrives.
For n people and d days, the probability of no collision is the product of 1 − k/d for k = 0, …, n−1. Subtract that product from one to get the probability of at least one collision. We condition on all previous birthdays being different at each step; we are not assuming that arbitrary pair matches are independent.
Run the cells in order. The first sets up Java imports and a double-precision reference; later cells share those definitions. After editing a definition, rerun it and the cells that use it. The default execution is CPU Java in the browser's emulated JVM. It requires no GPU. The first startup includes loading that runtime.
The function rejects negative group sizes and nonpositive year lengths. An empty room and a single person cannot contain a matching pair. More people than available days guarantees a collision, so we handle that case before multiplying anything.
1. Calculate the reference
The reference gives 0.475695308 for 22 people and 0.507297234 for 23. These are probabilities over repeated groups drawn under the model, not a promise that a particular room contains a match. A room with no matching birthdays is perfectly ordinary at 23 people.
Before continuing, predict what happens at 24. Should the probability rise by the same amount as it did between 22 and 23? Change the arguments in the print statement and check. The increment depends on how many unused days remain and on the probability that a collision has not already occurred.
A product written as a sum
Plain Java already solves the problem. The tensor version is useful because it exposes another way to express the same calculation: take logarithms of the factors, add them, and exponentiate the sum. The identity is product(x) = exp(sum(log(x))) for positive factors.
A tensor is an array with a shape. Here its entries count the people already in the room: zero through 22. Tensors.arange(23) creates those counts directly. Dividing by −365 and applying log1p gives the logarithms of the conditional probabilities. After summing them, expm1().neg() computes one minus the probability of no collision.
The short calculation uses our jax4j library. Jit.compile captures the tensor expression as a reusable execution plan; it does not turn this article into a GPU benchmark. Inputs and outputs are explicitly closed with Java's try-with-resources statement. The tracing machinery owns intermediates inside the captured expression.
2. Express the product as tensor operations
The arrows follow the Java chain. The first three stages keep 23 entries; sum() reduces them to one number. expm1() and neg() then turn that log-probability into the chance of at least one shared birthday. The diagram shows rounded reference values; it does not update when you edit the cell.
Expect a result close to the double reference, rather than identical last digits. This tensor calculation uses F32, the precision of Java float. The reference loop uses double, and its product evaluates operations in a different order.
This cell deliberately handles a group of 23 in a 365-day year. To change its group size, change the count in Tensors.arange(23) and rerun this cell. Keep it between 2 and 365. For zero or one person use the reference function; for more than 365 use the certainty argument. Sending negative factors into log is not a clever way to extend the formula.
The log1p(x) operation calculates log(1+x), and expm1(x) calculates exp(x)−1. They preserve small differences that can disappear when adding to or subtracting from one in F32. The calculation still has float rounding; these functions improve numerical accuracy without changing the probability model.
Where does the curve cross one half?
Looking at 23 alone leaves a small logical gap: perhaps an earlier group already crossed one half. A sweep closes it. Adding a person cannot remove an existing collision, so the probability is nondecreasing with group size. Search from zero and stop at the first crossing.
The sweep uses the double reference, keeping the threshold decision separate from the tensor demonstration. It is cheap enough to inspect directly; there is no need to hide the search inside another abstraction.
3. Find the first crossing
The first crossing is 23. Try a different threshold, such as 0.9, and predict whether the required group doubles. The curve is not linear: collisions accumulate quickly, then the remaining chance of a completely distinct room gets small.
This is a static reference plot for a 365-day year. Editing a cell changes its printed results; it does not redraw the illustration.
Why 253 pairs do not mean 253 independent trials
There are 23 × 22 / 2 = 253 unordered pairs in the room. If you fix your own birthday and compare everybody else against it, there are only 22 comparisons. That difference helps explain the surprise.
But the pair count is not an exact probability calculation. Pair events share people. If Alice matches Bob and Bob matches Carol, Alice necessarily matches Carol. Multiplying 253 independent no-match probabilities would therefore introduce an assumption the problem does not justify.
A useful mathematical aside: the expected number of matching pairs is exactly 253/365, about 0.693. Linearity of expectation allows this even though the pair events are dependent. An expected count of 0.693 is not a 69.3% chance of a match: some rooms contribute several matching pairs at once. Three people sharing one birthday already contribute three pairs.
Does anyone match your birthday?
Now fix one person in the room. Each of the other n−1 people independently avoids that person's birthday with probability 364/365. The answer is 1 − (364/365)^(n−1). Here the independence follows from the birthday draws once the target day is fixed.
The code uses double-precision log1p and expm1 for this second expression. log1p(x) evaluates the logarithm of one plus x; expm1(x) evaluates the exponential minus one. Their names are compact, but their purpose is to avoid throwing away small differences unnecessarily.
4. Ask about your birthday instead
At 23 people, the second probability is about 0.0586. The group includes you, so the exponent is 22. If instead you invite 23 other people, the exponent becomes 23. Naming the population is part of defining the experiment.
Try increasing the group to 100. Both probabilities rise, but they remain quite different. A match somewhere in the room has many opportunities; your particular date is only one target. Neither question is more correct. They measure different events.
A year with ten days
Changing the number of possible birthdays makes the mechanism easier to inspect. In a ten-day year, four people still have a collision probability below one half, while five cross it. Eleven people guarantee a collision by the pigeonhole principle: ten slots cannot accommodate eleven distinct birthdays.
Change days below and look for both landmarks: the first 50% crossing and the point of certainty. The reference function accepts any positive integer year length, although printing enormous sweeps is not a useful browser experiment. Keep this cell's year small.
5. Try a shorter year
The ten-day probabilities begin 0, 0, 0.1, 0.28, and 0.496 for groups of zero through four; five is the first crossing, at 0.6976. The cell lets you check every factor rather than infer a threshold from the shape of the curve.
For one final experiment, replace the fixed target date with two acceptable dates in the “your birthday” calculation. Write the no-match probability first, then check the result. Specify whether those dates must be distinct before choosing a formula.
For the array operations used here, continue with elementwise operations and broadcasting. For what capture and replay mean, see your first JIT compile.
The MIT Mathematics for Computer Science notes develop the birthday problem and probability reasoning. A formal proof of the 23-person bound supplies another reference for the threshold. All values in this article are calculated model probabilities, not observed birthday data.
