What Is a Live Scoring System?
A live scoring system ingests real-time score updates from sporting events, games, or competitions, maintains authoritative score state, and fans the updates out to potentially millions of concurrent viewers via WebSocket connections. The design challenges are threefold: ingesting updates reliably from multiple data providers, resolving conflicting or out-of-order events into a consistent state machine, and pushing low-latency updates to a massive number of connected clients without overloading backend services.
Requirements
Functional Requirements
- Ingest score events (goals, points, penalties, timeouts, game state changes) from official data providers via authenticated feeds.
- Maintain authoritative score state per game including current score, period/quarter/inning, elapsed time, and game status.
- Push incremental updates to all subscribed WebSocket clients within 2 seconds of ingestion.
- Allow clients to request full current state on connection to recover from missed updates.
- Support tens of thousands of concurrent live games and millions of concurrent viewers.
Non-Functional Requirements
- End-to-end latency (provider event to client receipt) under 2 seconds at p99.
- WebSocket connection capacity of 5 million concurrent sessions.
- Ingest pipeline availability of 99.99% during live event windows.
- Graceful degradation: clients fall back to 10-second HTTP polling if WebSocket fails.
Data Model
The Game record is the root entity: game ID, sport type, home and away team IDs, scheduled start time, current status (scheduled/live/final), venue, and data-provider game ID. The ScoreState document is stored in Redis and holds the mutable current snapshot: home score, away score, current period, game clock, and an ordered list of the last 50 scoring events. This document is the source of truth for what gets sent on initial WebSocket connection. The ScoringEvent record in PostgreSQL is the immutable append-only log of every event received, including provider timestamp, sequence number, event type, team, player ID, and delta score — used for replay, audit, and highlight generation.
Core Algorithms
Ingest and State Machine
Each sport defines a state machine governing valid transitions (e.g., a basketball game can go from live to halftime to live to final, but not from final to live). The ingest worker applies each incoming event against the state machine. Out-of-order events (detected via sequence number gaps) are held in a per-game reorder buffer (a min-heap sorted by sequence) for up to 500 ms before being applied in order or discarded as stale. Conflicting updates from multiple providers are resolved by preferring the official primary provider and logging the conflict for ops review.
Score State Update
The authoritative ScoreState is maintained in Redis as a JSON document updated with a Lua script to ensure atomicity: the script reads the current sequence number, validates that the incoming event sequence is greater, applies the delta, appends the event to the recent-events list, trims the list to 50 items, and increments a version counter. The version counter is used by the fan-out layer to detect whether clients are behind and need a full resync versus an incremental patch.
WebSocket Fan-Out
After the state update, the ingest worker publishes a scored patch message to a Redis Pub/Sub channel keyed by game ID. A fleet of WebSocket gateway servers each subscribe to all game channels relevant to their connected clients. On receiving a pub/sub message, the gateway iterates its local connection table for that game ID and writes the patch to each WebSocket. This local-delivery model avoids broadcasting to every gateway — each gateway only holds connections for games its clients are watching, reducing unnecessary cross-node traffic.
API Design
The WebSocket API uses a subscribe/unsubscribe protocol. On connection, clients send {"action":"subscribe","game_id":"g123"}. The gateway responds with a full_state message containing the current ScoreState, then streams score_update messages for each subsequent event. Clients can subscribe to multiple games per connection. The REST fallback API exposes GET /v1/games/{id}/score returning the current ScoreState JSON, intended for polling clients and server-side rendering. GET /v1/games/{id}/events?since_seq={n} returns all events after a sequence number, enabling missed-update recovery for WebSocket clients that reconnect.
Scalability and Infrastructure
WebSocket gateways are horizontally scaled behind a Layer-4 load balancer with sticky sessions per game. Each gateway process handles up to 50,000 concurrent WebSocket connections using an event-loop model (Node.js or Go goroutines). The Redis Pub/Sub cluster is sharded by game ID hash. For mega-events (World Cup finals, Super Bowl) with millions of viewers per game, the fan-out is offloaded to a dedicated high-fan-out tier: the gateway publishes to a Kafka topic partitioned by game ID, and a separate layer of edge push servers — colocated with CDN PoPs — consumes the topic and drives the actual WebSocket writes, distributing the fan-out load geographically and reducing origin gateway load by 90%.
{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “How do you design a real-time score ingestion pipeline?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Score events from data providers arrive via webhooks or a persistent TCP connection. A stateless ingestion service validates and normalizes each event, then publishes it to a partitioned Kafka topic keyed by game ID. Downstream consumers process events in order per partition. The ingestion layer enforces schema validation and rejects or dead-letters malformed payloads to prevent corrupt state from propagating.”
}
},
{
“@type”: “Question”,
“name”: “How is score state managed in a live scoring system?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Current game state (score, period, clock, possession) is held in Redis as a hash keyed by game ID for sub-millisecond reads. Each incoming event is applied as a state transition using a Lua script inside Redis to guarantee atomicity. A write-ahead log or event-sourced append-only store (Kafka or DynamoDB Streams) serves as the system of record, letting you reconstruct state at any point in time.”
}
},
{
“@type”: “Question”,
“name”: “How do you fan out score updates to millions of WebSocket clients?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “WebSocket servers are stateless gateways; each maintains an in-memory map of game_id to connected client sockets. A Pub/Sub layer (Redis Pub/Sub or a dedicated broker) broadcasts each score event to all gateway nodes subscribed to that game. Each gateway then iterates its local client list and pushes the update. This avoids a single bottleneck and scales horizontally — adding gateways increases fan-out capacity linearly.”
}
},
{
“@type”: “Question”,
“name”: “How do you handle backfill for clients reconnecting to a live scoring feed?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Each event is stamped with a monotonic sequence number per game. Clients include their last-seen sequence in the reconnect handshake. The server queries a short-lived event buffer (a sliding window kept in Redis Sorted Set or Kafka consumer with a time-bounded offset) and replays any missed events before resuming live delivery. This eliminates the state gap without requiring a full state snapshot for every reconnect.”
}
}
]
}
See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering
See also: Meta Interview Guide 2026: Facebook, Instagram, WhatsApp Engineering