LearnHLDDesign live cricket score fan out

Design live cricket score fan out

Last over of an IPL final. Tens of millions of people are watching a number that changes about once every thirty seconds, and every one of them wants it within a second of the ball being bowled.

The write rate is almost nothing. A few events per over from one scorer. The read rate is one of the largest concurrent workloads anyone runs. That ratio, roughly one write to a hundred million reads, is unlike anything else in this course, and it inverts the usual instincts: caching is easy here, and the hard part is the connection layer.

Your answer

30 million people watching one match. Would you push updates over open connections, or let clients poll every few seconds? Name the cost that decides it.

What you are building

In scope
  • Live score, wickets, overs and the current ball. A small document, a few hundred bytes, updated a few times a minute.
  • Deliver an update within about a second of the event. Later than the crowd noise next door and the product has failed.
  • Handle tens of millions of concurrent viewers. Concurrent, and arriving in a spike when a big wicket falls.
  • Correct a score after a review. Scores go backwards sometimes, and a design that assumes append only breaks.
The numbers you commit to
  • Peak 30 million concurrent viewers, reached within minutes.
  • Under 1 second from scorer input to a phone screen at p95.
  • A viewer must never see the score go backwards except for a real correction.
  • Cost per viewer matters. This runs for six weeks a year and idles the rest.
Cut, and say so out loud
  • Video streaming. Several orders of magnitude more bandwidth and a completely different design.
  • Commentary text and social feeds. Same delivery layer, much larger payloads.
  • Betting odds, which have latency and correctness requirements that would dominate the discussion.

The numbers

This is the page where the napkin math genuinely decides the architecture rather than confirming it.

Fan out cost
30 million
5 seconds
400 bytes
Polling: requests per second30M / 5s = 6,000,000
Polling: bandwidthpayload + ~600B of headers = 48 Gbps
Push: updates per secondone event per 30s = 1,000,000 sent
Push: bandwidth3.2 Gbps
Push: connection servers at 100k each300
15x
more bandwidth for polling than for pushing

Polling asks 6,000,000 times a second for a value that changes twice a minute, and each ask carries more header than payload. Push sends only when something happens. The catch is the other row: push needs about 300 servers holding connections open, and those servers exist whether or not anything is happening.

Drag the poll interval down to two seconds and watch the multiplier. Then remember that during the last over, users refresh manually, which is polling with no interval at all.

Push or poll

Delivery
Stateless, trivially scalable, and it survives anything. A short CDN cache collapses 30 million requests into a handful of origin fetches, so the origin barely notices. The cost is bandwidth at the edge and latency: a 5 second poll means up to 5 seconds of staleness, and the wicket that everyone reacts to arrives late.
The number that decides it

A poll costs roughly 600 bytes of request and response headers to deliver 400 bytes that usually have not changed. At 30 million viewers on a 5 second interval, that overhead alone is most of your bandwidth bill, and it is paid continuously whether or not a ball has been bowled. Push pays per event. The crossover is not close.

The design

Figure 1. One event enters on the left and multiplies twice: once across regional servers, then once more inside each one.

The shape to notice is that the multiplication happens as late as possible. One event reaches a few hundred edge servers, and only inside those servers does it become tens of millions of messages. Fanning out any earlier means moving that volume across the network instead of within a process.

Deep dive one: what the edge server actually does

At 100,000 connections on one machine, the work per event is 100,000 writes. Doing that naively is the difference between a machine that copes and one that falls over.

1/5 One message arrives per event, not per viewer. Every edge server subscribes to the same match topic, so the bus sends a few hundred messages rather than tens of millions.

That last point is the design decision that makes everything else possible. Send state, not events. A viewer who misses a message is not broken, they are just briefly one ball behind, and the next message fixes them with no replay, no acknowledgement, and no per viewer state to track.

Deep dive two: the spike is the workload

Traffic here is not steady. A wicket falls, everyone opens the app, and connections arrive in a wall.

Worse is the reconnect wave. If an edge server dies holding 100,000 connections, all of them reconnect at once. Without randomised backoff on the client, they arrive together at the remaining servers, push those closer to their limit, and can start a cascade.

Three defences, all cheap:

Randomised backoff on the client, which spreads a reconnect wave over minutes instead of seconds. The highest value code in this system lives on the phone.

Admission control at the edge. When a server is near capacity, refuse new connections immediately with a pointer elsewhere rather than accepting them and degrading everyone.

Degrade to polling. If the push tier cannot take a viewer, serve them the CDN snapshot. They get a score three seconds late instead of no score at all, and the CDN capacity is already there for the fallback path.

Deep dive three: the score that goes backwards

Third umpire overturns a decision. The score you already delivered is wrong.

Every update carries a monotonically increasing version from the ingest service, and clients ignore anything with a version lower than what they hold. That alone fixes the common ordering problem, where a retried or delayed message arrives after a newer one.

A correction is a new update with a higher version and a lower score. The version keeps increasing even though the score decreases, which is exactly why the version is a separate field and not the score itself. Clients that treat “score only goes up” as an invariant will show a stale score forever after the first review, and it will be a support ticket nobody can reproduce.

Break it

Match traffic
group stage
group stagefinal, 30Mwicket fallsserver diesdefended
Healthy. 4 million viewers on 50 edge servers. One event becomes 50 messages on the bus and 4 million socket writes spread across the fleet. Comfortable.

The API

GET/v1/matches/{id}/score
returns 200 { version: 214, runs: 178, wickets: 4, over: "18.4" } with Cache-Control: max-age=2
Why: The polling fallback, and it is a plain cacheable GET on purpose. A 2 second CDN cache turns tens of millions of requests into a handful of origin fetches, and this path has to keep working when the push tier is refusing connections.
GET/v1/matches/{id}/stream
returns 200 text/event-stream
Why: Server sent events rather than WebSocket. The traffic is entirely one directional, SSE reconnects automatically in the browser, and it is plain HTTP so it survives proxies that block upgrades. Choosing the simpler transport because the data only flows one way is worth saying out loud.
GET/v1/matches/{id}/stream?since=213
returns 200, current state first
Why: On reconnect the client sends the last version it holds and immediately receives the current state, not a replay of what it missed. Since every update is full state, there is nothing to replay and no per viewer cursor for the server to remember.

Trade-offs

ChoiceWhat you gainWhat you payPick it when
Push over pollingSub second delivery, and bandwidth proportional to events rather than to viewers times poll rate.A stateful connection fleet sized for peak concurrency that idles between matches.Latency is part of the product. For a score during a final, it is the product.
Full state per update over deltasA dropped message is self healing, there is nothing to replay, and the server keeps no per viewer position.Slightly larger payloads, which is nothing when the state is a few hundred bytes.Small state that changes wholesale. Deltas only pay off for large documents.
Server sent events over WebSocketSimpler, plain HTTP, automatic browser reconnect, and it passes through proxies that block upgrades.One directional, so anything the client needs to send takes a separate request.The data genuinely flows one way, which for scores it does.
CDN polling as a fallback tierThe push tier can shed load into it instead of failing, and it costs nothing when unused.Two delivery paths to build and keep consistent.Any system where peak is many times the average and graceful degradation is worth more than uniform latency.

Interview replay

Interviewer
30 million people watching one match. Push or poll?
They want the reasoning, not the answer. Both answers can be right if the reasoning is.
You
Push for the engaged viewers, with polling through a CDN as the default and the fallback. The score changes about twice a minute, so polling every five seconds asks about twenty billion times an hour for a value that changed a hundred times, and each ask carries more header than payload. Push sends only on events. But push needs a stateful fleet sized for peak concurrency, so I would keep the polling path so I can shed load into it rather than fail.
Gives the ratio that makes the decision, then immediately names what push costs. Both halves matter.
Interviewer
One edge server holds 100,000 connections. What does it do when a ball is bowled?
Checking whether the fan out is understood at the level of what the machine actually executes.
You
It receives one message from the bus, serialises and compresses it once, then writes those same bytes to every socket. Serialising per connection would be doing identical work a hundred thousand times. Connections that cannot keep up get the update dropped rather than blocking the loop, because one slow phone must not stall delivery for everyone else on that machine.
Serialise once and never block on a slow consumer are the two things that make this machine work. Both are concrete.
Interviewer
Is dropping updates acceptable?
Testing whether the earlier answer was considered or careless.
You
Here, yes, because every update carries the full current score rather than a delta. A viewer who misses one is a ball behind until the next update arrives, which is seconds later. That is the reason to send state rather than events: it makes loss self correcting and it means the server keeps no per viewer position at all.
Justifies it from a design decision made earlier rather than shrugging. This is the strongest exchange available in this question.
Interviewer
An edge server dies. Walk me through the next sixty seconds.
The reconnect storm, which is the failure that actually happens.
You
100,000 connections drop and try to reconnect. If they all use the same delay they arrive together, land on servers already near capacity and can cascade. So: randomised backoff on the client to spread them over minutes, admission control so a near full server refuses immediately instead of accepting and degrading everyone, and refused viewers fall back to the CDN snapshot. They get a score two seconds late rather than nothing.
Three defences at three layers, and the degradation path is named rather than implied.
Interviewer
The third umpire reverses a decision and the score drops. Any problem?
A domain specific trap that catches people who assumed monotonicity.
You
Only if the client assumed the score only goes up. Every update carries an increasing version from ingest and clients ignore anything older than what they hold, which handles out of order and retried messages. A correction is a higher version with a lower score. That is exactly why the version is a separate field rather than being derived from the score.
Answers the trap and explains why the design already handled it, which is better than proposing a fix.

Checkpoint

Checkpoint

1. Why is polling so much more expensive than push here?

2. Why send the full score in every update rather than a delta?

3. An edge server holding 100,000 connections dies. What is the danger?

Say this in 60 seconds

The defining ratio is one write to roughly a hundred million reads, so this is a fan out problem and not a storage problem. I would push over server sent events to engaged viewers and keep CDN backed polling as both the default for casual viewers and the fallback under stress, because polling asks constantly for a value that changes twice a minute and pays more in headers than payload. One event goes to a few hundred edge servers, and only inside each server does it become a hundred thousand socket writes, so the multiplication happens as late as possible. Each server serialises once and reuses the bytes, and drops updates for connections that cannot keep up rather than blocking, which is safe because every update carries full state rather than a delta, so a missed message self corrects. Updates carry an increasing version so a review that lowers the score still moves forward. And the failure I would design for is an edge server dying: a hundred thousand synchronised reconnects, handled with randomised client backoff, admission control, and degrading to the polling path rather than refusing service.

IndGeek provides solutions in the software field, and is a hub for ultimate Tech Knowledge.