Introduction

You are on a game show. There are three doors. Behind one is a car; behind the other two are goats. You pick door 1. The host — who always knows where the car is — opens one of the other doors to reveal a goat. He then offers you a choice: stick with door 1, or switch to the remaining closed door.

What do you do?

Most people say it doesn't matter. After all, two doors remain and one hides the car, so the odds must be 50-50. This intuition is wrong — and it was wrong even for many professional mathematicians. In 1990, when columnist Marilyn vos Savant published the correct answer in Parade magazine, she received nearly 10,000 letters disagreeing with her, including from PhDs. She was right. Switching wins 2/3 of the time.

The Monty Hall problem — named after the host of the US game show Let's Make a Deal — is one of the most famous probability puzzles ever devised. Its answer is proven, not controversial. Understanding why it's true is a lesson in conditional probability that reshapes how you think about information.

Try It: Switch vs. Stay

Play the game yourself, or run thousands of automated rounds and watch the win rates converge. The simulation runs the exact Monty Hall setup: random car placement, host always reveals a goat from the unchosen doors, you either switch or stay.

<p class="hint">Pick a door, the host reveals a goat, then decide: <b>switch</b> or <b>stay</b>. Run thousands of rounds automatically to see the win rates converge.</p>
<div class="doors" id="doors"></div>
<div class="msg" id="msg">Choose a door to start.</div>
<div class="btns" id="btns">
  <button id="switchBtn" type="button" disabled>Switch door</button>
  <button id="stayBtn" type="button" disabled>Stay</button>
</div>
<div class="divider"></div>
<div class="stats">
  <div class="stat-block">
    <div class="bar-label">Switching</div>
    <div class="bar-wrap"><div class="bar bar-switch" id="barSwitch"></div></div>
    <div class="stat-nums" id="numsSwitch">0 wins / 0 rounds</div>
  </div>
  <div class="stat-block">
    <div class="bar-label">Staying</div>
    <div class="bar-wrap"><div class="bar bar-stay" id="barStay"></div></div>
    <div class="stat-nums" id="numsStay">0 wins / 0 rounds</div>
  </div>
</div>
<div class="btns auto-btns">
  <button id="auto100" type="button" class="ghost">Run 100 rounds</button>
  <button id="auto1000" type="button" class="ghost">Run 1,000 rounds</button>
  <button id="resetAll" type="button" class="ghost danger">Reset all</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.doors { display: flex; gap: 12px; justify-content: center; margin: .5rem 0 .7rem; }
.door { width: 74px; height: 110px; border-radius: 10px; border: 2px solid #adb1b8;
        background: #c9ccd1; display: flex; flex-direction: column; align-items: center;
        justify-content: flex-end; padding-bottom: 10px; font-size: 1.1rem; font-weight: 700;
        cursor: pointer; user-select: none; transition: all .15s; position: relative; }
.door:hover:not([disabled]) { background: #bcc0c6; }
.door[disabled] { cursor: default; }
.door .emoji { font-size: 2.2rem; position: absolute; top: 18px; opacity: 0; transition: opacity .3s; }
.door.open .emoji { opacity: 1; }
.door.chosen { border-color: #1d3557; background: #dbe8f5; }
.door.goat-reveal { border-color: #e8a838; background: #fdf3dc; cursor: default; }
.door.win { border-color: #0a7d33; background: #d4f0e0; }
.door.lose { border-color: #c92f3c; background: #fde8ea; }
.door-num { font-size: .85rem; color: #555; }
.msg { font-size: 1rem; font-weight: 600; min-height: 1.4em; margin: .3rem 0 .5rem; text-align: center; }
.msg.ok { color: #0a7d33; }
.msg.bad { color: #c92f3c; }
.msg.info { color: #1d3557; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; justify-content: center; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem;
         border: 1px solid #1d3557; background: #1d3557; color: #fff;
         border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button.danger { border-color: #c92f3c; color: #c92f3c; }
button:disabled { opacity: .4; cursor: default; }
.divider { border-top: 1px solid #e0e4e8; margin: .9rem 0; }
.stats { display: flex; flex-direction: column; gap: .5rem; margin-bottom: .7rem; }
.stat-block { display: flex; align-items: center; gap: .6rem; }
.bar-label { width: 62px; font-size: .85rem; font-weight: 600; color: #333; text-align: right; }
.bar-wrap { flex: 1; background: #e8eef3; border-radius: 6px; height: 22px; overflow: hidden; }
.bar { height: 100%; border-radius: 6px; transition: width .4s ease; width: 0%; }
.bar-switch { background: #1d6d3a; }
.bar-stay { background: #c92f3c; }
.stat-nums { font-size: .8rem; color: #555; width: 130px; }
.auto-btns { margin-top: .3rem; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Monty Hall simulation
// - Car placed randomly behind one of 3 doors (uniform 1/3 each)
// - Host always reveals a goat from the unchosen doors (never the car, never your pick)
// - Switch = move to the remaining closed unchosen door
// - Stay  = keep original pick
// Expected: switch wins 2/3, stay wins 1/3

const DOORS = [0, 1, 2];
let state = null; // { car, chosen, revealed, final, won, strategy }
let switchWins = 0, switchTotal = 0, stayWins = 0, stayTotal = 0;

function randInt(n) { return Math.floor(Math.random() * n); }

function simulate(strategy) {
  const car = randInt(3);
  const chosen = randInt(3);
  // Host reveals a goat: pick from doors that are not chosen and not the car
  const revealable = DOORS.filter(d => d !== chosen && d !== car);
  const revealed = revealable[randInt(revealable.length)];
  // Remaining door (not chosen, not revealed)
  const remaining = DOORS.find(d => d !== chosen && d !== revealed);
  const finalDoor = strategy === 'switch' ? remaining : chosen;
  const won = finalDoor === car;
  return { car, chosen, revealed, final: finalDoor, won, strategy };
}

function renderDoors(s) {
  const el = document.getElementById('doors');
  el.innerHTML = '';
  DOORS.forEach(i => {
    const d = document.createElement('div');
    d.className = 'door';
    if (!s) {
      d.addEventListener('click', () => onChoose(i));
    } else {
      if (i === s.revealed) {
        d.className = 'door goat-reveal open';
      } else if (s.final !== undefined) {
        if (i === s.final) d.className = 'door ' + (s.won ? 'win open' : 'lose open');
        else if (i === s.car && !s.won) d.className = 'door win open';
        else d.className = 'door';
      } else {
        if (i === s.chosen) d.className = 'door chosen';
      }
    }
    const emoji = document.createElement('span');
    emoji.className = 'emoji';
    if (!s) {
      emoji.textContent = '';
    } else if (i === s.revealed) {
      emoji.textContent = '🐐';
    } else if (s.final !== undefined) {
      emoji.textContent = (i === s.car) ? '🚗' : '🐐';
    }
    const num = document.createElement('span');
    num.className = 'door-num';
    num.textContent = fmt('Door %i%%v%', { i: i, v: 1 });
    d.appendChild(emoji);
    d.appendChild(num);
    el.appendChild(d);
  });
}

function setMsg(t, cls) {
  const m = document.getElementById('msg');
  m.textContent = t;
  m.className = 'msg ' + (cls || '');
}

function setBtns(switchEn, stayEn) {
  document.getElementById('switchBtn').disabled = !switchEn;
  document.getElementById('stayBtn').disabled = !stayEn;
}

function updateStats() {
  const sw = switchTotal ? (switchWins / switchTotal) : 0;
  const st = stayTotal ? (stayWins / stayTotal) : 0;
  document.getElementById('barSwitch').style.width = (sw * 100).toFixed(1) + '%';
  document.getElementById('barStay').style.width = (st * 100).toFixed(1) + '%';
  document.getElementById('numsSwitch').textContent =
    fmt('%switchWins% wins / %switchTotal% rounds (%v%%)', { switchWins: switchWins, switchTotal: switchTotal, v: switchTotal ? (sw * 100).toFixed(1) : '—' });
  document.getElementById('numsStay').textContent =
    fmt('%stayWins% wins / %stayTotal% rounds (%v%%)', { stayWins: stayWins, stayTotal: stayTotal, v: stayTotal ? (st * 100).toFixed(1) : '—' });
}

function onChoose(i) {
  const car = randInt(3);
  const chosen = i;
  const revealable = DOORS.filter(d => d !== chosen && d !== car);
  const revealed = revealable[randInt(revealable.length)];
  state = { car, chosen, revealed, final: undefined };
  renderDoors(state);
  setMsg(fmt('Door %revealed%%v% is a goat! Switch or stay?', { revealed: revealed, v: 1 }), 'info');
  setBtns(true, true);
}

function onDecide(strategy) {
  const remaining = DOORS.find(d => d !== state.chosen && d !== state.revealed);
  state.final = strategy === 'switch' ? remaining : state.chosen;
  state.won = state.final === state.car;
  state.strategy = strategy;
  if (strategy === 'switch') { switchTotal++; if (state.won) switchWins++; }
  else { stayTotal++; if (state.won) stayWins++; }
  renderDoors(state);
  setMsg(state.won ? '🎉 You won the car!' : fmt('🐐 Goat. The car was behind door %car%%v%.', { car: state.car, v: 1 }),
         state.won ? 'ok' : 'bad');
  setBtns(false, false);
  updateStats();
}

function runAuto(n) {
  for (let i = 0; i < n; i++) {
    const s = simulate(i % 2 === 0 ? 'switch' : 'stay'); // alternate evenly
    if (s.strategy === 'switch') { switchTotal++; if (s.won) switchWins++; }
    else { stayTotal++; if (s.won) stayWins++; }
  }
  state = null;
  renderDoors(null);
  setMsg(fmt('Ran %n% rounds. Switch: %v%% | Stay: %v2%%', { n: n, v: (switchWins/switchTotal*100).toFixed(1), v2: (stayWins/stayTotal*100).toFixed(1) }), 'info');
  setBtns(false, false);
  updateStats();
}

document.getElementById('switchBtn').addEventListener('click', () => onDecide('switch'));
document.getElementById('stayBtn').addEventListener('click', () => onDecide('stay'));
document.getElementById('auto100').addEventListener('click', () => runAuto(100));
document.getElementById('auto1000').addEventListener('click', () => runAuto(1000));
document.getElementById('resetAll').addEventListener('click', () => {
  switchWins = switchTotal = stayWins = stayTotal = 0;
  state = null;
  renderDoors(null);
  setMsg('Choose a door to start.', '');
  setBtns(false, false);
  updateStats();
});

renderDoors(null);
updateStats();

// Verify the math: after many simulations, switch should win ~2/3 and stay ~1/3
// (logged to console for the node verification step)
let testSW = 0, testST = 0, testSWW = 0, testSTW = 0;
for (let i = 0; i < 90000; i++) {
  const s = simulate('switch'); testSW++; if (s.won) testSWW++;
  const t = simulate('stay');   testST++; if (t.won) testSTW++;
}
console.log('VERIFY switch win rate:', (testSWW/testSW).toFixed(4), '(expected ~0.6667)');
console.log('VERIFY stay  win rate:', (testSTW/testST).toFixed(4), '(expected ~0.3333)');
Result
Console

After a few hundred rounds the pattern is unmistakable: staying wins roughly 1 in 3 times; switching wins roughly 2 in 3. The more rounds you run, the closer the bars get to those exact fractions. This is not a trick of the simulation — it is the mathematical truth playing out in real time.

The Real Math

The result is proven — not a simulation artifact. Here is why, presented three ways:

The case-by-case argument

Label the doors 1, 2, 3. You pick door 1. Each of the three cases is equally likely (probability 1/3):

  1. Car is behind door 1 → host opens 2 or 3 → switching loses.
  2. Car is behind door 2 → host must open door 3 → switching wins.
  3. Car is behind door 3 → host must open door 2 → switching wins.

Two out of three cases mean switching wins. That's 2/3 — exact, not approximate.

The information argument

When you first picked, you had a 1/3 chance of being right. The host's reveal carries new information: he never opens the car door, so he is forced to avoid it. That constraint transfers the 2/3 probability that the car is not behind your door onto the single remaining unchosen door.

Bayes' theorem

Let H3H_{3} = "host opens door 3", C1C_{1} = "car is behind door 1". After the host opens door 3:

P(C1C_{1} | H3H_{3}) = P(H3H_{3} | C1C_{1}) · P(C1C_{1}) / P(H3H_{3}) = (1/2 · 1/3) / (1/2) = 1/3

The other door therefore holds the car with probability 2/3.

The key is that the host is not picking randomly — he is constrained by knowledge. A randomly-opening host who happened to reveal a goat would give you 50-50. Knowledge changes probability.

See how Bayesian inference generalizes this logic to any belief-updating scenario, or read why P vs NP asks whether finding answers is as easy as checking them.

Where It Matters

The Monty Hall problem is a gateway to conditional probability — the probability of an event given that something else is already known. That idea appears everywhere:

  • Medical testing: a positive test result does not mean you have the disease. The probability depends on the prevalence of the disease (the prior) and the false-positive rate. Ignoring the prior is called the base-rate fallacy, and it has led to real policy mistakes.
  • Legal reasoning: the prosecutor's fallacy confuses P(evidence | innocent) with P(innocent | evidence). The Monty Hall intuition — that what the host knows and does changes probabilities — is exactly what juries sometimes fail to grasp.
  • Spam filters: a Naive Bayes classifier updates the probability that an email is spam given each word it sees. The update rule is Bayes' theorem applied thousands of times per second.
  • Machine learning: every probabilistic model — from Gaussian mixture models to large language models — is doing structured belief-updating. The intuition starts here.
  • Decision theory: once you know that information changes probabilities, you can ask: how much is a piece of information worth? That question drives optimal experiment design, A/B testing and clinical trial methodology.

Master the Monty Hall problem and you have the key insight of Bayesian inference: new information reshapes old beliefs, often in ways that defy gut feeling.

Conclusion

The Monty Hall problem has a clean, proven answer: always switch. You win 2/3 of the time. The counter-intuitive feeling that it should be 50-50 comes from ignoring what the host knows — and that knowledge is the whole puzzle.

The deeper lesson is that probability is about information, not just counting outcomes. When an agent with knowledge acts on that knowledge, the probabilities seen by an observer shift. The Monty Hall host is a tiny, friendly version of the same mechanism that powers Bayesian classifiers, medical diagnostics and scientific inference.

So if you ever find yourself on a game show with three doors, you know what to do. And if you ever find yourself updating a belief in the light of new evidence, you now know the mathematics behind why that update might surprise you.

Share this article

Pick a channel — or use your device's native share sheet.

Comments

Loading comments...

https://www.kipuhub.com/en/article/monty-hall/Content licensed under CC BY-NC 4.0.