Simulating Chance

Simulating Dice Rolls in Python

Build a short Python dice roller, count the results, and compare simulated frequencies with the exact values for one die and for the total of two dice.

On this page
  1. Rolling one die
  2. What to expect
  3. Rolling two dice
  4. Comparing simulation with exact values
  5. Rolling different dice
  6. Using a seed
  7. What dice simulations show, and what they do not

Rolling one die

The smallest useful simulation rolls a single fair six-sided die many times and counts how often each face appears. Python’s standard library has what you need:

import random
from collections import Counter

rolls = 6000
counts = Counter(random.randint(1, 6) for _ in range(rolls))

for face in range(1, 7):
    print(face, counts[face], counts[face] / rolls)

random.randint(1, 6) returns a whole number from 1 to 6, with both ends included. The Counter tallies how often each value appears, and dividing by the number of rolls gives a relative frequency.

What to expect

For a fair die, each face has probability 1/6, or about 0.1667. With 6,000 rolls, you expect about 1,000 of each face.

You will not get exactly 1,000 each time. The count for any one face varies with a standard deviation of the square root of (6,000 x 1/6 x 5/6), which is the square root of about 833.3, roughly 29. So counts between about 940 and 1,060, which is within two standard deviations, are completely ordinary. Seeing a face with 960 and another with 1,045 does not mean the die is loaded.

If your program prints the same count for every face, or counts that are all suspiciously close to 1,000, that would be the odd result. Real random data has scatter.

Rolling two dice

A more interesting case is the total of two dice. Change the roll to:

total = random.randint(1, 6) + random.randint(1, 6)

Each die is rolled separately, so the two are independent. Then count totals from 2 to 12.

The exact probabilities can be found by listing the 36 equally likely pairs. The number of pairs giving each total is:

Total Ways Probability
2 1 1/36, about 2.8%
3 2 2/36, about 5.6%
4 3 3/36, about 8.3%
5 4 4/36, about 11.1%
6 5 5/36, about 13.9%
7 6 6/36, about 16.7%
8 5 5/36, about 13.9%
9 4 4/36, about 11.1%
10 3 3/36, about 8.3%
11 2 2/36, about 5.6%
12 1 1/36, about 2.8%

The ways add up to 36, which is a quick check that nothing is missing: 1+2+3+4+5+6+5+4+3+2+1 = 36. This is the reason that seven is the most common of the two dice totals, a point explored on its own elsewhere.

Comparing simulation with exact values

A neat way to build the comparison into the program is to compute the exact probabilities by brute force and print them next to the simulated ones:

import random
from collections import Counter

rolls = 36000
simulated = Counter(
    random.randint(1, 6) + random.randint(1, 6) for _ in range(rolls)
)

(exact counts by listing all 36 outcomes)
exact = Counter(a + b for a in range(1, 7) for b in range(1, 7))

print("total  simulated  exact")
for total in range(2, 13):
    sim_share = simulated[total] / rolls
    exact_share = exact[total] / 36
    print(f"{total:5}  {sim_share:9.4f}  {exact_share:5.4f}")

With 36,000 rolls, the expected number of sevens is 36,000 x 6/36 = 6,000, and the expected number of twos is 1,000. Simulated shares typically agree with the exact ones to within about a percentage point or less, and the match tends to be tighter the more rolls you use. The number of trials needed depends on how precise you want to be.

If the simulated shares are far off, look for a bug. A classic mistake is rolling one die and doubling it, which gives only even totals, and another is using randint(1, 7), which gives sevens as a face.

Rolling different dice

The same pattern extends to the polyhedral dice used in tabletop games. For a die with sides faces, use random.randint(1, sides). To roll several dice and sum them, wrap the call in a function:

def roll(num_dice, sides):
    return sum(random.randint(1, sides) for _ in range(num_dice))

Then you can test questions like “what is the chance that three six-sided dice sum to at least 15?” by running roll(3, 6) many times and counting. For small cases you can verify by brute-force enumeration of all outcomes, as above, which is a good habit.

Using a seed

If you want the same results every run, call random.seed(1) once at the top. This is useful when sharing code or debugging, because pseudo-random generators produce a repeatable sequence from the same seed. Remove the seed when you want fresh numbers.

What dice simulations show, and what they do not

The simulation demonstrates that long-run frequencies approach the exact probabilities, and that short-run counts bounce around. It also shows that each roll is independent: a face appearing several times in a row does not make it less likely next time, which is the gambler’s fallacy in action. A simulation of a fair die cannot tell you whether a real physical die is fair; that requires collecting real rolls and testing them.

Dice games with real stakes carry a house edge or other disadvantage for the player, and gambling can be harmful. This article is about checking the maths, not about finding a way to win. If gambling is causing problems, a local support service can help.

The complete guide to simulating chance

Simulating Probability with Simple Code
Read the guide

Keep exploring

Guides from other topics

All topics →