โ Case study ยท Product design & build ยท 2026
A World Cup fantasy pool for 27 friends and family: everyone drafts twelve national teams, real match results score the points, and the highest total takes the pot. I built it to replace the spreadsheet we used in 2022, then ran it for the full tournament: 104 matches scored automatically, a daily digest email, and a 148 to 148 finish settled by a tiebreaker.
โ The game
All 48 World Cup teams are ranked into 12 tiers of four by their betting odds. Each entrant picks one team from every tier: a roster of twelve, from a favorite like Spain down to a lottery ticket like Iraq.
Your teams earn points from real results: wins and draws in the group stage, more for advancing, the most for wins deep in the knockout bracket.
Teams in tiers 7โ12 also score a point per goal, and any win over a higher-tier team pays an upset bonus. Even a doomed pick can earn its keep.
Picks locked at kickoff and went public. $100 a head, with the pot split between the top two overall and the two group-stage leaders.
All 48 teams as seeded for 2026, in betting-odds order within each tier. Tiers 7โ12 earn the per-goal bonus. Hover a flag for the country.
โ The brief
The last World Cup, this pool ran on a Google Form and a hand-scored spreadsheet. Everything was manual: picks came in by form, scores were typed in by hand, and standings went out whenever the commissioner got around to it. And because everyone drafted from the same short list of favorites, the scores bunched together. Half the field sat within a few points of each other, and a Tuesday afternoon group match mattered to nobody.
The 2026 version fixed both problems with the rules themselves: the tier draft spreads everyone across all 48 teams, a live data feed does all the scoring, and the underdog bonuses keep even a lost cause worth watching. It had to be ready before kickoff on June 11. Picks went live eleven days after the first commit.
โ The design
Nobody was going to sit at a computer for this. People check the pool in bed, at work, and in front of the TV, so every screen was designed for a phone and for a specific moment of the day.
Pick twelve teams in one sitting without opening a rulebook in another tab. The full tier sheet fits on one screen.
What happened yesterday, who moved, and what's on today. One email at 7am, no login required.
Live scores with the stakes attached: whose teams are playing and what each goal is worth on the board.
โ The season
The system saved a copy of the standings every morning, 44 snapshots in all. The tiers did what they were designed to do: the lead changed hands among eight different entrants. Miller led for 19 days, longer than anyone. Michael carried the lead into the final with no teams left alive. And Zach, 26th of 27 on June 13, rode Spain back up the board and won the pool on a tiebreaker, 148 to 148.
Every line is a real entrant; hover to explore. Nightly ranks, June 12 to July 20.
| # | Entrant | Total | Underdog | Upset |
|---|---|---|---|---|
| 1 | Zach F. ๐ | 148 | 50.5 | 20 |
| 2 | Jeff S. ๐ฅ | 148 | 50 | 17 |
| 3 | Drew K. | 143 | 49 | 23 |
| 4 | Michael C. | 140 | 56 | 20 |
| 5 | Dan S. | 136 | 43 | 17 |
| 6 | Adam H. | 131 | 62.5 | 25 |
| 7 | Arnie S. | 128.5 | 30.5 | 14.5 |
| 8 | Miller I. | 128 | 60 | 23 |
| 9 | David S. | 128 | 56.5 | 19 |
| 10 | Matthew F. | 127.5 | 48.5 | 14.5 |
| 11 | Charlie F. | 127 | 61.5 | 17 |
| 12 | Josh B. | 125.5 | 44.5 | 18.5 |
| 13 | Evan S. | 120 | 47 | 19 |
| 14 | Jack K. | 120 | 43 | 10 |
| 15 | John I. | 116 | 57 | 20 |
| 16 | Tim H. | 115.5 | 46.5 | 18.5 |
| 17 | Mike S. | 113.5 | 50.5 | 19.5 |
| 18 | Tom W. | 113.5 | 45.5 | 13.5 |
| 19 | Aaron F. | 112.5 | 40.5 | 12.5 |
| 20 | Nick F. | 112 | 47.5 | 12 |
| 21 | Travis R. | 110.5 | 56.5 | 24.5 |
| 22 | Paul B. | 108.5 | 35.5 | 9.5 |
| 23 | Cooper B. | 100.5 | 46.5 | 16.5 |
| 24 | Phil P. | 98.5 | 39.5 | 10.5 |
| 25 | Pete K. | 95.5 | 39.5 | 10.5 |
| 26 | Bradley B. | 94.5 | 36.5 | 16.5 |
| 27 | David R. | 93 | 35 | 16 |
โ The machine
One scheduled job ran everything: pull the latest results, rescore the pool, and close each day with a recap. The order was deliberate, with standings saved before new results arrived so "who moved today" had a baseline, and the write-up last, after every number was checked.
Every three minutes, the system asks a live sports-data feed for the latest scores, schedules, and betting odds.
Every entry is rescored from zero on every pass: 1,679 itemized points, checked by 199 automated tests, so the totals can never quietly drift.
A simulation plays out the rest of the tournament 10,000 times, using live betting odds, and rates each entry's shot at winning.
When the day's last match ends, the verified stats go to Claude, an AI model that writes the morning recap in a pundit's voice. It phrases the numbers; it can't add any.
The digest emails everyone who opted in: results, movers, upsets, and today's schedule. No login required.
Every night the entire database is compressed and emailed to me. For a pool this size, an inbox is a perfectly good backup.
โ The AI
The morning digest was written every night by Claude, under one hard rule: it could only narrate numbers the scoring engine had already verified. Across 34 digests it never misreported a score. Here's how it called the final:
"At the top it could not be tighter. Zach F. leads on 148, dead level with Jeff S. also on 148, with Drew K. right behind at 143. That is a two-man photo finish for the crown. [โฆ] Spain are champions and the group table is basically decided by whoever picked them. That's the tournament, folks."
โ The odds
After every match, a simulation replayed the rest of the tournament 10,000 times and scored all 27 entries in each imaginary run, using the same engine as the real leaderboard. Team strength started from the bookmakers' pre-tournament odds and repriced as results came in, and games about to kick off used the live betting line.
Every entry wore one of these ratings on the leaderboard. The design rule: the middle four are deliberately coarse, never a fake-precise percentage, and only the two extremes were ever stated as fact. No shot meant mathematically eliminated; Clinched meant first place was locked no matter what.
Going into the final, simulation gave way to arithmetic. With only two outcomes left, the engine played both out exactly: if Spain won, Zach took the pool on a tiebreaker at 148 to 148. If Argentina won, Michael jumped from fourth to champion at 151. Spain won.
โ Security & trust
A pool among friends still handles real money and private picks, so it got the same care as a real product.
Sign-in is a one-time email link. Nothing to store, nothing to leak, nothing for 27 people to forget.
Who can see what is enforced by the database itself, not just the interface. Picks stayed hidden until the tournament locked, and outsiders got nothing even if they queried the database directly.
The browser never held anything sensitive. Admin credentials lived server-side, the scheduled jobs required their own secret, and the developer backdoor didn't exist outside my machine.
A monitor compared every incoming result against the recomputed board and would have emailed me on any mismatch. It stayed quiet all tournament.
The database plan had no built-in backups, so a nightly job emailed me a full copy. The restore plan was an inbox search.
At the end: the schedules removed, a check that no secrets ever entered the public code, names shortened for privacy, and a final audit that outsiders can read nothing personal.
โ The finish
Final totals for all 27 entrants: a 55-point spread, settled by a tiebreaker only at the very top. In 2022, half the field finished within a few points of each other. Hover for names.