close

DEV Community

john lee
john lee

Posted on • Originally published at world-cup-prediction-game-psi.vercel.app

I built a million-scale World Cup prediction game on DynamoDB + v0 in a weekend

I created this project and this post for the **H0: Hack the Zero Stack* hackathon
(Vercel v0 + AWS Databases). #H0Hackathon*

Live demo: https://world-cup-prediction-game-psi.vercel.app
60-second walkthrough: https://youtube.com/shorts/tTPotTmi4fE


A World Cup is one of the few events where you genuinely might get hundreds of millions
of people doing the same thing at the same time. So when I set out to build a prediction
game for it, the question wasn't "can I make a CRUD app" — it was "would the data layer
survive matchday?" That's exactly the bet H0 asks you to make: prototype on the same
Amazon DynamoDB foundation real products run on, with a v0-scaffolded Next.js
frontend on Vercel.

Here's how it came together — and the one design decision the whole thing hinges on.

The app

Every match shows a 10,000-run Monte-Carlo forecast (win/draw/loss + a likely
scoreline) next to what the crowd actually picked. You call the result, earn points
(+5 exact score, +3 right outcome), and climb a global leaderboard that settles
against real results pulled from ESPN. No login — an anonymous cookie makes every visitor
a player instantly.

v0 did the frontend; I owned the data layer

v0 scaffolded a genuinely good Next.js App Router app — match cards, a pick sheet, three
tabs, SWR data fetching against /api/* — in minutes. It even left placeholder route
handlers with a literal TODO: "wire to DynamoDB." My job was to make that real without
fighting the contract v0 generated. I dropped in the sim engine + a DynamoDB data layer
and rewrote the four routes to return the exact shapes v0's TypeScript types expected.
The UI never knew the difference — it just started showing real fixtures.

The decision the whole thing hinges on: the leaderboard

Single-table DynamoDB is the easy part. The trap is the leaderboard. The naive design —
"put every user in one partition, sort by points" — is a hot partition waiting to
happen: on matchday every score update hammers one partition key.

So the leaderboard is write-sharded. A user's profile is indexed under
LB#<season>#<hash(uid) % 10> with their (zero-padded) points as the sort key. Writes
spread across 10 partitions; to read the global top-N I scatter-gather — query the
top-N of each shard in parallel and merge. A user's rank is COUNT(points > mine) summed
across shards. It's the pattern AWS literally recommends, and it means the board scales
horizontally instead of melting.

One GSI does triple duty (all-string keys):

  • LB#<season>#<shard> → leaderboard
  • DATE#<yyyymmdd> → today's slate
  • MPICK#<matchId> → every pick on a match (so settling fans out cheaply)

Picks bump an atomic counter on the match (that's the "crowd picked" split) inside a
transaction. On-demand capacity means I never pre-provision — matchday spikes just work.

Shippable, not a demo

The thing that makes me happiest: it's actually deployed and serving real data
real 2026 fixtures, real sim odds, a real sharded leaderboard on real AWS — and it plugs
into a daily-content funnel I already run that sends real fans to it. The whole point of
the "zero stack" is that the weekend prototype is the production foundation. This one is.

Try it: https://world-cup-prediction-game-psi.vercel.app · built with v0 + Vercel +
Amazon DynamoDB for #H0Hackathon.

Top comments (0)