Dev Learning

Live score push

Design a service that pushes live sports scores to millions of people watching the same match at the same time. A score update should reach everyone within a second or two.

Where’s the slack?

Delivery: 1–2 seconds. Connection setup: seconds. Reconnect: seconds.

A second or two of slack, bounded by something outside your system: the person in the next room shouting at their television.

The physical world sets this delivery budget, not engineering taste. If a neighbour with a TV feed shouts before your app updates, the product has failed in a way users notice viscerally. Broadcast feeds themselves run several seconds behind live, which gives you a little room, but not much. Call it one to two seconds end to end.

That is more slack than it first appears, and it is worth spending on purpose. It means you do not need microsecond delivery, you can batch updates into small windows, and you can accept a message taking a couple of hundred milliseconds to traverse a fan-out layer.

Connection setup has seconds of slack. A short pause when the app opens is fine. This matters more than it sounds, because it is what lets you stagger reconnections after a deploy instead of accepting a million simultaneous handshakes.

Historical data, statistics and commentary have minutes of slack and should be plain cached HTTP requests, not pushed. Mixing them into the live path is an easy way to over-build this design.

So: One to two seconds means you can batch updates into ~250 ms windows, which cuts fan-out work dramatically for a delay too short to notice. It also means the live channel should carry only what is actually live. Everything else is a cacheable request.

Work it through

Five stages, gated. You write your answer for each stage before its model answer unlocks. That is the core of the method.

Loading your progress…

Reference material for this problem. The diagrams below are here to check your own against once you have drawn it, not to read first.

The lifecycle

ReceivedproviderDeduplicatedsystemState updatedsystemPublishedfan-outDeliveredconnection serverDuplicate eventdeduplicate on provider event idProvider goes downshow last known state with a visible timestampCorrection — goal disallowedpush full current state, so it self-healsClient in a tunnelreconnect with last-event-id, fetch current state to catch upDeploy drops everyoneslow roll plus jittered client backoffSlow client fills its bufferbounded buffer — drop the client, never the server
A score event, provider to phone. The correction branch forces full-state pushes, and that one decision simplifies three other branches.

The numbers

Provider events~1 / minute
Concurrent connections10,000,000
Deliveries in the 2s after a goal~5–10M / sec, in a burst

There is no ingestion problem here at all. So the hard part is burst fan-out and holding ten million mostly-idle connections — including the moment they all reconnect at once.

One message a minute in. Ten million deliveries in two seconds out.

The architecture

on connectScore providerIngest svcdedupe, correctionsMatch statecurrent, cachedPub/subone channel per matchConn server100k socketsConn server100k socketsViewersSSECached HTTPinit + catch-up
One message in, ten million out. The multiplication happens on the connection servers, which already hold the sockets — never in the pub/sub layer.

The mechanism

Authorposts onceFan-outInbox 1Inbox 2Inbox 3
one write inmany writes out — reads later are free
Publish once per match; each connection server multiplies it across the sockets it already holds. Publishing per user would be ten million broker messages per goal.

The decision this turns on

A
Push full current state
  • —Every message is idempotent — a duplicate changes nothing.
  • —A correction just works; the next message overwrites the wrong value.
  • —Catch-up after a disconnect is simply "fetch current state" — no replay log.
  • —Slightly larger messages, which at 300 bytes is irrelevant.
B
Push increments ("goal scored")
  • —Smallest possible payload.
  • —One missed message and the client is permanently wrong, silently.
  • —Corrections need an explicit undo message and ordering guarantees.
  • —Catch-up needs a replay log with per-client positions.
Which one: Full current state. The payload difference is meaningless at this size, and it removes the need for ordering guarantees, a replay log, and correction handling: three subsystems deleted by one decision. When state is this small, push state.
What you put in the message. This choice decides how hard three other problems are.

Follow-ups you should expect

Many interviews are decided in the follow-ups, not the first diagram.

Same shape, other products

The server has to speak first, to a lot of people, at the same time.

Capacity is measured in concurrent connections, not requests per second, and the hard part is the fan-out layer, not the socket.

Others with this shape: Live sports score · Live comments · Collaborative editor · Presence

Before you leave
Try this problem again in a week without reading anything. Comparing what you write then with what you wrote today shows you what stuck.
Stuck, or new to this?Read the full worked solutionAll five stages end to end, with the diagrams and the costs, written out the way you would say it. Best read after you have had a go.Read it →

How it is really built

Write your own answer first. Then read how teams who run this at scale describe it. Their constraints were real, and the write-ups often say what their choices cost.