2,147,483,648 World Cups, One Weekend
With the 2026 World Cup Round of 32 about to be set, I wanted to build something themed around it. My first instinct was the obvious one: a predictor. Pick your team, see their odds of winning it all. Every sports site already does this and it felt flat the moment I wrote it down.
What kept nagging at me instead was a framing question. Once the Round of 32 is locked, the knockout stage is just 31 single-elimination matches. That means the whole tournament can only unfold in 2^31 distinct ways. That's a real, countable number: 2,147,483,648. Not "millions of possible outcomes" as a vague phrase. An exact number, and every one of those 2.1 billion timelines has an exact probability once you know each team's strength.
That's a much more interesting app than a predictor. Not "who wins" but "how many realities are still alive, and which one are we in."
The Doctor Strange hook
Once I had the framing, the reference wrote itself. In Infinity War, Doctor Strange looks through 14,000,605 possible futures to find the one where they win. That's a number small enough to be funny next to 2,147,483,648. The app leans into it directly: right now, before a single match is played, you're looking at a superposition of 2.1 billion brackets. Every real result the tournament produces collapses that number, exactly in half, until only one timeline is left standing.
So the product became: a live counter of how many World Cup realities remain, that ticks down as actual results come in.
The math: Elo, Poisson, then exact DP
The whole thing rests on keeping three layers cleanly separated, each one consuming only the output of the one before it.
1. Team strength (Elo). Every team gets a rating from the World Football Elo system. This is the only place where "how good is this team" lives.
2. Match model (Poisson). From the Elo gap between two teams, I derive an expected-goals rate for each side using a supremacy split: roughly 130 Elo points of edge is worth about one extra goal. Each side's goal count is modeled as a Poisson variable, and summing over the joint distribution of scorelines gives win probability, draw probability, the single most likely scoreline, and a confidence score, all from the same underlying numbers, so they can never contradict each other.
3. Bracket math (exact dynamic programming). This is the part that made the project worth building. Because the bracket is fixed going in, you don't need Monte Carlo simulation to answer "what's the chance this team wins it all." You can compute it exactly with a bottom-up DP over the bracket tree: the probability a team wins its round-of-32 match, times the probability its potential opponents get there too, weighted by matchup odds, recursively up to the final. It runs in well under a millisecond in the browser, and it's exact, not sampled.
Here's what that collapse looks like in numbers, stage by stage:
| Stage | Matches decided | Realities still alive |
|---|---|---|
| Round of 32 set (kickoff) | 0 / 31 | 2,147,483,648 |
| After the Round of 32 | 16 / 31 | 32,768 |
| After the Round of 16 | 24 / 31 | 128 |
| After the Quarterfinals | 28 / 31 | 8 |
| After the Semifinals | 30 / 31 | 2 |
| After the Final | 31 / 31 | 1 |
Every completed match fixes one binary choice and halves what's left. That halving is the whole mechanic of the site.
Architecture: slow model, fast bracket
I split the system into two layers that update on very different clocks.
engine/ (Python, slow-moving) web/ (Next.js + TypeScript, live)
---------------------------- -----------------------------------
Elo ratings reads model.json (committed)
| reads results.json (live, as matches finish)
Poisson goal model |
| exact bracket DP: superposition + collapse
advance-probability matrix re-run on every click
| writes
model.json ------------------------------->
The Python side only runs when I want to retune the model, and it writes a
static model.json bundle. The TypeScript side never touches Elo or Poisson
directly, it just reads the bundle and does the exact combinatorics live, in
the browser, on every result update or hypothetical click. That separation
means swapping in a better-trained model later (something trained on Kaggle
match data, say) is a one-file change. The bracket engine and the entire UI
stay untouched.
Results themselves come from results.json, kept current three ways: a
GitHub Action that polls football-data.org every six hours and opens a PR for
me to review and merge, a one-line CLI command for manual entry by FIFA match
number, or editing the JSON directly for anything unusual (like a
penalty-shootout draw, which the API reports as a tie and I have to resolve
by hand).
What the weekend actually looked like
The build itself moved fast once the math was settled, because the hard part was never the DP, it was making the UI communicate a number like 2.1 billion without it feeling abstract.
A few things that took longer than expected:
- The champion ribbon. The bracket converges into a single "champion" banner at the center, and getting the flag and the country name to sit as one visually centered unit, without overlapping at different name lengths, took several passes. Small thing, disproportionate amount of fiddling.
- Save-as-image. Turning the live bracket into a shareable static image (for posting outside the app) meant re-rendering the whole 32-team bracket to a canvas server-side, not just screenshotting the DOM.
- Performance on the bracket-pool queries. Two fun modes, Chalk (draw a random bracket from the 50 most likely outcomes) and Chaos (draw one from the 50 most cursed, upset-everywhere outcomes), originally recomputed too much on every draw. Reworking how those pools were built brought that path down by roughly 85x.
- Making the live counter actually live. Wiring the GitHub Action so a finished match becomes a merged PR becomes a redeployed counter, without me babysitting it every six hours.
None of that is exotic engineering. It's the normal shape of a weekend project: the core idea takes an afternoon to prove out, and then most of the remaining time goes into the fifteen small things that make it feel finished instead of like a prototype.
Why exact math instead of simulation
It would have been easier to just run 100,000 Monte Carlo simulations and report the frequencies. I didn't want to, for a specific reason: the whole premise of the app is "every number here is exact," and a simulated percentage undercuts that premise even if it's numerically close. If I'm telling someone their team has a 1 in 4,096 chance of a specific run to the final, I want that to be a fact derived from the model, not an estimate with sampling noise. The DP makes that true, and it's fast enough that there's no performance excuse not to.
Where it stands
It's live at bracket.aweshislam.com, static export on Vercel, no login, no backend beyond the results file. You can look up your team's round-by-round odds, check whether two teams can even meet before the final, or just watch the realities-remaining counter shrink as results land.
The tournament isn't over yet, so this post is only the build story. Once the final whistle blows I'll write the follow-up: how the model actually did, called-correctly tallies, exact-scoreline hits, and whatever the data says about whether Elo-plus-Poisson was good enough for a job like this.
Keep reading
Jul 6, 2026
I Built a Site That Roasts Your CV
A weekend idea: what if a CV review felt less like feedback and more like getting dragged by four different people who all hate your resume for different reasons. 233 roasts and 23 battles later, here's the build behind getroasted.live: the model fallback chain, the persona system, and the guardrails that keep 'savage' from turning into 'reported'.
Jul 6, 2026
I Built a Telegram Remote for Claude Code, Then Let It Build Itself
I'd been running Claude Code over SSH from my phone through Termius during commutes, and it was barely tolerable. Then three straight days of dawats left me with zero laptop time and a project pipeline that couldn't pause. So I built Tgmux: a Telegram bridge to Claude Code running in tmux. Then I used it, from my phone, to build the rest of itself.