← Praruj Thapa
/
Project · Forecasting · 2026

Forecasting the 2026 World Cup with 100,000 simulated tournaments

Who wins a 48-team tournament is too tangled to work out by hand, so I let the computer play it 100,000 times and counted. Then I did the part most forecasts skip: I wrote the call for the final down before kickoff and scored it against what happened.

19.7%
Spain's title chance before a ball was kicked, the highest of 48 teams. Spain won.
57.8%
Spain's chance against Argentina in the final, logged before kickoff. Spain won 1–0 after extra time.
0.178
Brier score on that call: 0 is perfect, 0.25 is a coin flip
The forecast before the tournament

Title chances from 100,000 simulated tournaments. The short line across the end of each bar is the 95% confidence interval: at this many runs it is at most about ±0.25 points, so the ranking is not noise.

Spain19.7%WON THE TITLEArgentina12.3%France11.8%England8.5%Brazil6.8%Portugal6.5%Belgium6.2%Germany5.4%Netherlands4.3%USA3.1%Morocco3.0%Croatia2.3%

Top 12 of 48 teams; seed 42. Even the favourite wins only about one tournament in five, which is the honest answer for a sport this random.

How one match is decided

Ratings

Each team has an Elo rating (Spain 2095, Argentina 2040). The gap between two ratings sets how likely each side is to win.

Draws

Group matches can end level: 26% of the time when two teams are equal, less as the gap grows. Knockout matches cannot, so extra time and penalties fall back to the rating gap.

Hosts

USA, Mexico and Canada get +80 rating points in their own matches, in the range the football-analytics literature reports for home advantage.

The tournament

12 groups of 4 play round robin; the top two and the 8 best third-placed teams go to a 32-team knockout. All of it is played 100,000 times with a fixed random seed, so every number can be reproduced.

Three versions, each fixing the last
  • 1. A single script. Straight into a knockout bracket with no group stage and no draws. Fast, and quietly wrong: skipping the group stage removes the matches where favourites most often slip, so it overstated them.
  • 2. A tested package. Real group stage, three-outcome matches, home advantage, a seeded random generator and a 95% interval on every number, covered by 18 pytest tests in CI.
  • 3. A live bracket. Once the real knockout draw was fixed (7 July), it simulated only what was still undecided, from the actual bracket.
Where the model is blind

On 7 July the live bracket gave Norway 0.2%, though Norway had just knocked out Brazil to get there. The ratings are fixed before the tournament, so a team playing above its rating stays “unlikely” until someone updates the numbers by hand.

Team (bracket of 7 July)Title chance
Spain31.4%
Argentina22.5%
France22.3%
England18.2%
Belgium1.7%
Morocco1.6%
Colombia1.0%
USA0.7%
Switzerland0.3%
Norway0.2%

Live bracket of 7 July, 100,000 simulations, seed 7 (top 10).

What I learned
  • Write the prediction down before the result. A forecast you only check afterwards proves nothing. That is why the final was called before kickoff, and the README keeps that record as the template for future tournaments.
  • One correct call is one data point. A Brier score of 0.178 on a single match is encouraging, not proof of calibration; that needs many logged forecasts.
  • Report the uncertainty with the number. “Spain 19.7%” means little without “±0.25 at 100,000 runs” next to it, and “wins one in five” is the more honest headline.
Stack
Python (standard library)Monte Carlo simulationElo ratingsWilson intervalsmultiprocessingpytest + GitHub Actions