OS-009 · Probability

Monty Hall Problem Simulation

A car is behind one of three doors and goats are behind the others. You pick a door; the host, who knows where the car is, opens another door to show a goat and offers you the switch. Switching wins two games in three. The lab plays both strategies on the same games — and lets you swap in a host who opens doors blindly, which changes the answer to one half.

Interactive model

OS-009 · monty-hall · seeded
your pick (thick outline)door left closed for a switchcarcar revealed by a blind host
switchstayswitch, exactstay, exact
Parameters
One car, d − 1 goats; the host opens d − 2 doors
Same seed, same run — here, in simulation.js and in simulation.py.
Games counted
10,000
Discarded (host revealed the car)
0
Win rate if you stay
32.97%
Stay, exact
33.33%
Win rate if you switch
67.03%
Switch, exact
66.67%
Standard error of a win rate
0.47%

Example run: default parameters, seed 42.

Why switching wins when the host knows

Your first pick is right with probability 1/d. The host then opens d − 2 of the other doors and never opens the car. If your pick was wrong — probability (d − 1)/d — the car is behind one of the other doors, and the one door he leaves closed must be it. So switching wins exactly when your first pick was wrong:

P(stay wins) = 1/d    P(switch wins) = (d − 1)/d

With three doors that is 1/3 against 2/3; with ten doors the host opens eight goat doors and switching wins 90% of the time. In every counted game exactly one of the two strategies wins, so the two simulated rates always add up to 100%. Steve Selvin posed the three-door problem in a 1975 letter to The American Statistician.

Why the host’s knowledge matters

Switch the host to opens doors at random. He still opens d − 2 of the doors you did not pick, but without looking. With probability (d − 2)/d he reveals the car — a third of all games with three doors — and those games are thrown out. In the games that remain, the car is behind your door with probability 1/d and behind the other closed door with probability 1/d, so each strategy wins half the time.

Bayes’ rule shows why. Before the host acts, the odds that your pick is right are 1 : (d − 1). The observation “only goats were revealed” has probability 1 if your pick is right. If it is wrong, a knowing host still shows only goats with probability 1, but a blind host does so with probability only 1/(d − 1). With the knowing host the observation carries no information and the odds stay 1 : (d − 1); with the blind host it multiplies them to 1 : 1.

The doors and goats you see can be identical in both versions. The probability depends on the process that produced what you see, not only on what you see.

How each game is simulated

  1. Place the car behind a uniformly random door; the player picks a uniformly random door.
  2. Choose the one other door that stays closed. A knowing host must keep the car’s door closed when the player picked a goat; otherwise, and always for a blind host, the door is uniform over the d − 1 doors not picked. The host opens the rest.
  3. If a blind host opened the car’s door, discard the game. Otherwise staying wins when pick = car and switching wins when the closed door hides the car.

Discarding games is the simulation’s way of conditioning on “the host revealed only goats”. Step plays one game and describes it; Play runs games in batches.

Example result

With seed 42, 10,000 three-door games with a host who knows where the car is: staying won 32.97% of games and switching won 67.03%, against exact values of 33.33% and 66.67%. The standard error of each rate is 0.47%.

Output of monty-hall with doors=3 games=10000 host=knows seed=42
Games counted10,000
Discarded (host revealed the car)0
Win rate if you stay32.97%
Stay, exact33.33%
Win rate if you switch67.03%
Switch, exact66.67%
Standard error of a win rate0.47%

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
// One game with d doors. The car is behind a uniformly random door and the player
// picks a uniformly random door. The host then opens d − 2 of the other doors, so
// exactly one other door stays closed: switching means moving to that door.
export function playGame(d, host, rng) {
  const car = rng.int(d);
  const pick = rng.int(d);
  let other;
  if (host === 'knows' && pick !== car) {
    other = car; // the host must avoid the car, so the car's door is the one left closed
  } else {
    // A knowing host with the car behind the player's door, or a host who opens
    // doors blindly: the door left closed is uniform over the d − 1 other doors.
    other = rng.int(d - 1);
    if (other >= pick) other++;
  }
  // A blind host reveals the car when it is behind neither closed door.
  const revealed = car !== pick && car !== other;
  return { car, pick, other, revealed, stayWins: car === pick, switchWins: car === other };
}

export function exactRates(d, host) {
  // Knowing host: stay wins only if the first pick was right. Blind host, given the
  // car was not revealed: P(pick = car) = 1/d and P(other = car) = 1/d, so 1/2 each.
  return host === 'knows' ? { stay: 1 / d, switch: (d - 1) / d } : { stay: 0.5, switch: 0.5 };
}
Python — simulation.py
def play_game(d, host, rng):
    """One game: returns (stay_wins, switch_wins, car_revealed).

    The host opens d - 2 of the doors the player did not pick, leaving exactly one
    other door closed; switching means moving to that door."""
    car = rng.int(d)
    pick = rng.int(d)
    if host == "knows" and pick != car:
        other = car  # the host must avoid the car
    else:
        other = rng.int(d - 1)  # uniform over the d - 1 doors not picked
        if other >= pick:
            other += 1
    revealed = car != pick and car != other  # only a blind host can do this
    return car == pick, car == other, revealed


def exact_rates(d, host):
    """Knowing host: 1/d and (d-1)/d. Blind host, car not revealed: 1/2 each."""
    return (1 / d, (d - 1) / d) if host == "knows" else (0.5, 0.5)

Run the full model from a terminal:

Shell
node simulation.js --doors 3 --games 10000 --seed 42
python simulation.py --doors 3 --games 10000 --seed 42

Download the model

monty-hall.zip6 files · 12.8 KBDownload .zip
Files in monty-hall.zip
FileContentsSize
README.mdHow to run it, parameters, example result2.3 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+)8.0 KB
simulation.pyPython 3.9+ port, standard library only6.6 KB
example.csvResult table from the example run10.3 KB
LICENSE.txtMIT License1.0 KB

Assumptions & limitations

  • Standard rules. The host always opens d − 2 doors and always offers the switch. A host who offers the switch only when you picked the car would make switching lose every time; with such a host there is no fixed answer.
  • A knowing host with no favorite door. When you picked the car, the knowing host chooses the door to leave closed at random. A host with preferences does not change the overall 1/d and (d − 1)/d, but does change the odds given which particular doors he opened.
  • Uniform car placement. The car is equally likely to be behind any door.
  • Estimates, not proofs. The simulated rates converge on the exact values like 1/√n; the exact values come from the argument above.

References

  1. Selvin, S. (1975). A problem in probability (letter to the editor). The American Statistician, 29(1), 67. doi.org/10.1080/00031305.1975.10479121
  2. Grinstead, C. M. & Snell, J. L. (1997). Introduction to Probability, 2nd ed. American Mathematical Society. math.dartmouth.edu/~prob/prob/prob.pdf