Building a real-time leaderboard that holds up when the whole chat shows up
A leaderboard with prize money on it can be a little slow. It can never be wrong. Here is how I build them so they stay right when the traffic spikes.
Most of my client work is leaderboards for streamers in the gambling-affiliate world, like the one I built for KrispyGambles. There’s real money on the line every month, and the traffic is never steady. The site is quiet for hours, then a streamer mentions it and the whole chat opens it within the same minute.
That shape of traffic changes how you build. Here’s what I’ve learned.
Correct beats fast
A leaderboard that updates a second late is fine. A leaderboard that shows someone in first place when they finished third is a support ticket, an angry chat and a streamer who looks bad in front of their community.
So every decision below starts from the same rule. The numbers on the page must always match the numbers in the database, even if that means they arrive a moment later.
Compute once, not once per viewer
The obvious version recalculates standings every time someone loads the page. That works with ten viewers. With a thousand viewers it means a thousand identical sort queries, all at the moment the server is busiest.
Instead, standings are worked out once whenever new wager data comes in. The result is stored as a ready-made snapshot with a version number. Every viewer gets that same snapshot. A thousand viewers now cost almost the same as one.
Pick the simplest live connection that works
There are three common ways to get updates to the page:
- Polling. The page asks for new data every few seconds. Simple, and fine for slow-moving data, but most requests come back with nothing new.
- Server-Sent Events. One long-lived connection, and the server pushes updates down it. Data only flows from the server to the browser.
- WebSockets. A two-way connection. Great for chat or games, more moving parts to run.
A leaderboard only needs data flowing one way. Viewers watch, they don’t send anything back. Server-Sent Events fit that well, reconnect on their own when a phone drops signal, and go through nginx without special treatment beyond turning off buffering for that route.
A minimal Flask version looks like this:
@app.get("/stream")
def stream():
def events():
seen = None
while True:
snap = current_snapshot()
if snap.version != seen:
seen = snap.version
yield f"data: {snap.json}\n\n"
time.sleep(1)
return Response(events(), mimetype="text/event-stream")
The browser side is a few lines with EventSource. If the connection can’t be made at all, the page falls back to polling, so nobody ends up staring at a frozen list.
Decide the tie-breaker before launch
Two players on exactly the same wagered amount will happen, usually at the top, usually on the last day. Write the rule down before the competition starts and show it on the page. The one I like is “whoever got there first ranks higher”. It’s fair, and it’s easy to explain in chat.
Be exact about when a period ends
“Monthly” sounds clear until a player in another country asks why the board reset while it was still the 31st for them. Pick one fixed moment in UTC, show a countdown to it, and freeze the final standings at that moment. Prizes get paid from the frozen copy, never from a board that might still be moving.
Never trust the browser
Everything a viewer sees comes from the server. The page doesn’t calculate ranks or totals, and nothing it sends can change them. It sounds obvious, but it’s easy to slip in when you’re adding a “your position” widget in a hurry.
Make movement readable
When ranks change, rows should slide into their new place, not jump. Changes that happen in quick succession get grouped into one update, so the list doesn’t flicker during a busy minute. People watch these boards on stream, and a calm list reads as a trustworthy one.
Test with the real traffic shape
Load testing with a steady stream of requests misses the point. Test the spike: hundreds of connections opening in the same few seconds, while new wager data arrives. That’s the moment that breaks things in production, so that’s the moment to rehearse.
What clients notice
Nobody compliments the architecture. What they notice is that the board was right on the last night of the month, under load, with money on it. That’s the part that brings the next client.
If you run a community with a leaderboard, or want one, here’s what I’ve built so far, and this is how to reach me.