System Design Interview: Design a Live Sports Score System

What Is a Live Sports Score System?

A live sports score system delivers real-time score updates to millions of concurrent users during sporting events. The challenge is fan-out: one score update for a popular match (Super Bowl, Champions League final) must reach 10–50 million users within 1–2 seconds. The system must also handle massive read spikes (everyone opens the app at kickoff) while minimizing latency and infrastructure cost.

  • Netflix Interview Guide
  • Cloudflare Interview Guide
  • LinkedIn Interview Guide
  • Meta Interview Guide
  • Snap Interview Guide
  • Twitter Interview Guide
  • System Requirements

    Functional

    • Ingest score events from official data providers (Sportradar, Stats Perform)
    • Deliver score updates to subscribed users within 1–2 seconds
    • Support millions of concurrent subscriptions across thousands of active matches
    • Display match metadata: teams, current score, time, match status
    • Historical scores: completed match results accessible for weeks

    Non-Functional

    • Fan-out: 50M users subscribed to one match get the update in <2 seconds
    • Cold start: massive traffic spike at match start (everyone opens the app simultaneously)
    • Low cost: sports scores are a commodity feature — infrastructure must be cost-efficient

    Data Ingestion

    Score events come from third-party data providers via WebSocket or REST webhooks. The ingestion service receives events, validates, and publishes to an internal Kafka topic score-events. Events are keyed by match_id to ensure ordering within a match.

    score_event: {
      match_id, home_score, away_score, minute, event_type, timestamp
    }
    

    Event Processing and State Management

    A Kafka consumer group processes score events and maintains current match state in Redis:

    • match:{match_id}:state → Hash with current score, time, status
    • TTL: 24 hours after match ends
    • Updated atomically on each score event

    A separate consumer writes all events to a time-series database (Cassandra keyed by match_id + timestamp) for historical access.

    Fan-Out Architecture — The Core Challenge

    When a goal is scored, 50M users watching the same match need the update. Three approaches:

    Option 1: Push via WebSocket + Redis Pub/Sub

    Each client maintains a WebSocket connection to a connection server. Connection servers subscribe to Redis pub/sub channels (one per match). When a score update arrives in Kafka, the processor publishes to Redis channel match:{match_id}. All connection servers subscribed to that channel receive the message and push it to their connected clients.

    • Scale: 50M connections across 1000 connection servers = 50K connections/server. Manageable with Go or Node.js (async I/O).
    • Redis pub/sub bottleneck: a single Redis node can handle ~100K pub/sub messages/sec. For very popular matches, shard by match_id across Redis nodes.
    • Cold start: connection servers crash if 50M users connect simultaneously. Pre-scale before match start using scheduled auto-scaling.

    Option 2: Server-Sent Events (SSE)

    SSE is a simpler alternative to WebSockets for one-way server-to-client streaming. Clients open an HTTP connection that stays open; server pushes events as text/event-stream. No WebSocket upgrade needed, works through most proxies and load balancers. Lower overhead than WebSockets for pure push scenarios. Used by Twitter for its streaming API.

    Option 3: Short Polling / Long Polling (Fallback)

    For clients that can’t maintain persistent connections (some corporate firewalls block WebSockets). Long polling: client sends request, server holds it until a new score is available, responds, client immediately re-requests. Adds latency but works universally. Serve from CDN-cached endpoints with 2-second cache TTL for most clients to reduce server load.

    Handling the Cold Start Spike

    At match kickoff, 50M users open the app simultaneously. The initial request pattern:

    1. Fetch match metadata (static — CDN cached, no origin hit)
    2. Fetch current score (Redis read — fast, O(1))
    3. Establish WebSocket connection to connection server

    Pre-warming strategy: 30 minutes before kickoff, scale connection servers to target capacity. Redis match state pre-populated from the scheduling system. CDN cache warmed by pre-fetching match metadata.

    CDN for Score Reads

    Most users visit scores pages (read), not just subscribe to live updates. Serve the current score from a CDN endpoint with a 5-second TTL. The CDN caches the JSON response: /api/score/{match_id}. Score processor publishes updates → CDN cache invalidated via API on each score change. 95% of score page loads are CDN hits — only 5% reach origin. This dramatically reduces server load during popular matches.

    Push Notification Integration

    For users not actively in the app: send push notifications (iOS APNs, Android FCM) on goals and final whistle. Use the notification service (fan-out via Kafka consumer → notification worker → APNs/FCM). Personalized: only notify users who have this match in their “followed matches” list. Batch notifications with 30-second deduplication window to avoid spamming users.

    Database Design

    matches: id, home_team, away_team, start_time, competition, status, venue
    match_scores: match_id, home_score, away_score, minute, updated_at
    match_events: id, match_id, event_type, minute, player, team, detail, created_at
    user_subscriptions: user_id, match_id, notification_prefs
    

    Interview Tips

    • Fan-out scale is the crux — walk through connection count math: 50M users / 1000 servers = 50K connections/server.
    • Distinguish WebSocket (bidirectional, real-time) vs. SSE (server push, simpler) vs. polling (universal but slower).
    • CDN caching score reads is a force-multiplier — mention it to show you care about cost-efficiency.
    • Pre-scaling before match start is a key operational concern that differentiates senior-level answers.

    {
    “@context”: “https://schema.org”,
    “@type”: “FAQPage”,
    “mainEntity”: [
    {
    “@type”: “Question”,
    “name”: “How do you fan out a single score update to 50 million concurrent users?”,
    “acceptedAnswer”: { “@type”: “Answer”, “text”: “The fan-out architecture uses WebSockets + Redis pub/sub. Each client device maintains a persistent WebSocket connection to one of many connection servers (e.g., 1000 nodes, each handling 50K connections = 50M total). When a score event arrives, the score processor publishes to Redis pub/sub channel match:{match_id}. All connection servers subscribed to that channel receive the message (typically under 10ms) and push it to their connected clients. The limiting factor is Redis pub/sub throughput (~100K messages/sec per Redis node) and connection server memory. For mega-events, shard by match_id across Redis nodes. Use Go or Node.js for connection servers — their async I/O models efficiently handle tens of thousands of concurrent idle WebSocket connections per process.” }
    },
    {
    “@type”: “Question”,
    “name”: “How do you handle the massive traffic spike when a popular match starts?”,
    “acceptedAnswer”: { “@type”: “Answer”, “text”: “Pre-scale before the event. 30 minutes before kickoff: scale connection servers to 2x expected peak (use scheduled auto-scaling triggered by the match schedule database). Pre-warm Redis with match state. Pre-warm CDN caches for match metadata pages. Use a "slow start" in the load balancer: new connection server instances ramp up their connection count gradually rather than immediately receiving full load. For the initial app open (when 50M users open simultaneously): serve match metadata from CDN (no origin hit). Redis handles the initial current-score fetch (single O(1) read). WebSocket connection establishment is the bottleneck — spread it with exponential jitter on client reconnect logic so not all clients connect at exactly kickoff second.” }
    },
    {
    “@type”: “Question”,
    “name”: “What is the difference between WebSocket, Server-Sent Events, and long polling for live updates?”,
    “acceptedAnswer”: { “@type”: “Answer”, “text”: “WebSocket: bidirectional, persistent TCP connection. Client and server can both send messages at any time. Best for low-latency two-way communication (chat, gaming). For scores (server-push only), it's slightly over-engineered but widely supported. Server-Sent Events (SSE): one-way server-to-client over HTTP. Simpler protocol, works through standard HTTP infrastructure and proxies without special configuration. Reconnects automatically on disconnect. Best for pure server-push scenarios like scores, stock prices, news feeds. Long polling: client sends request, server holds it until a new event or timeout (30s), responds, client immediately re-requests. Universal compatibility (no special protocol needed). Higher latency (up to hold timeout) and server resource overhead (each waiting request holds a server thread or connection). Use as fallback for environments that block WebSockets.” }
    }
    ]
    }

    Scroll to Top