The Question
FE Design

Design a High-Performance Live Sports Scoreboard Dashboard

Design a scalable, real-time live sports scoreboard dashboard web application that displays dozens of concurrent matches with dynamic score changes, timing updates, and detailed play-by-play timelines. Focus on your architectural design for handling high-frequency live push updates (e.g., via SSE/WebSockets) without compromising 60 FPS UI rendering performance. Discuss component state management, decoupling network inputs from UI rendering, state synchronization, client-side caching, virtualized lists for concurrent matches, connection reconnection resilience, accessibility guidelines (aria-live), and structural decoupling of business domain rules from UI libraries.
React
Tailwind CSS
Radix UI
SSE
TypeScript
IndexedDB
Web Workers
Questions & Insights

Clarifying Questions

Q1: What is the scale and frequency of real-time score updates during peak times?
Assumption: The system handles 50-100 concurrent live matches during peak hours, with events (score changes, clock ticks, fouls) occurring up to 5-10 times per second across all active matches. Individual users only receive updates for matches currently in their viewport or active detail view.
Q2: What real-time transport mechanism is preferred or available on the backend?
Assumption: Server-Sent Events (SSE) will be used for unidirectional live data feeds (scores, timeline events) because of its native browser reconnection support and light overhead. HTTP/3 REST polling will serve as a graceful fallback.
Q3: Does the dashboard require a heavy visualization component (like a 2D/3D field mapping player positions)?
Assumption: No, the MVP focuses on tabular text, progress bars, state indicators, and chronological events. Graphical field visualization is out of scope for the MVP to prioritize performance and rapid delivery.
Q4: What are the target platforms and devices?
Assumption: Responsive web dashboard optimized for both desktop and mobile web browsers (iOS/Android Safari/Chrome), targeting poor network connections (3G) through lightweight payloads.
---

Crash Strategy

To design a high-frequency, highly reliable Live Sports Scoreboard, we must address the critical rendering and network challenges of live-streaming data:
How do we handle high-frequency UI updates without dropping frames?
Strategy: Introduce a push-based reactive state model that decouples real-time stream incoming ticks from UI component render cycles. We will implement visual throttling (e.g., batching DOM updates using requestAnimationFrame) and virtualize the match list to keep the DOM tree lightweight.
How do we prevent connection dropouts from corrupting or desynchronizing the scoreboard state?
Strategy: Use a sequence-numbered event stream. Every live payload contains a monotonically increasing sequence ID. If the client disconnects and reconnects, it sends its last received ID to fetch missed delta logs, ensuring absolute data integrity.
How do we optimize accessibility (`a11y`) for fast-paced visual updates?
Strategy: Implement controlled aria-live="polite" regions on score change events, allowing screen readers to announce major highlights (e.g., goals) without overwhelming users with clock-tick notifications.
---

Elite Bonus Points

Web Worker Event Decoupling: Offload incoming WebSockets/SSE parsing, payload decompression, and raw state delta reconciliation to a background Web Worker. This ensures the main UI thread is dedicated solely to rendering smooth interactions.
CSS-Only Animators: Use GPU-accelerated CSS keyframe transitions for flash cues (e.g., yellow flash for card, green flash for goal) instead of JS-driven animations to minimize CPU churn.
Network-Aware Stream Throttling: Utilize the browser's Network Information API to dynamically downgrade update frequencies (e.g., polling fallback or omitting minor updates like dynamic possession stats) when a user's connection degrades.
---
Design Breakdown

Requirements

Functional Requirements

Live Match Dashboard: View a clean, categorized list of ongoing, upcoming, and completed matches.
Real-Time Scores & Clock: View instantaneous score updates and live game-clock time ticks.
Detailed Match Viewer: Click a match to view real-time statistics (e.g., possession, shots) and a chronological text-based timeline of key events.
League/Sport Filters: Fast navigation to filter matches by League (e.g., EPL, NBA) or Sport type.

Non-Functional Requirements

Latency: UI updates must render within 250ms of network packet arrival.
Performance: Zero-lag scrolling and page interaction, sustaining a stable 60 FPS under high event-frequency.
Scalability: Capable of keeping 1,000+ matches in the client state without memory leaks.
Accessibility (A11y): Fully navigable via keyboard, meeting WCAG 2.1 AA criteria, utilizing proper aria attributes.
Offline Resilience: Gracefully notify users of offline states and auto-reconnect with local cache fallback.
---

Design Summary

Concise Summary

A decoupled, high-frequency reactive architecture featuring a Web-Worker-powered background event processor that ingests Server-Sent Events and batches UI mutations through a normalized client cache, keeping the main thread free for smooth, virtualized rendering.

Major Components

AppShell: Root container providing the overall global navigation, header, and system-level layout.
ScoreboardLayout: Multi-pane grid system arranging sport categories, live card lists, and detailed event views.
LiveDashboardPage: Core container coordinating dynamic filter actions and state layouts.
MatchListContainer: Virtualized collection manager that optimizes DOM footprint of live match summaries.
MatchCard: High-performance display component rendering live scores, dynamic state transitions, and time indicators.
MatchDetailContainer: Multi-tab wrapper showing key statistics and timelines for a chosen match.
StatsTab: Real-time progress bar layout comparing game metrics.
EventTimeline: Chronological feed list showcasing game events.
RealtimeSubscriptionService: Singleton managing SSE transport, reconnection protocols, and heartbeats.
ScoreQueryManager: Local normalized state manager caching active game records.
ActiveMatchStore: Simple reactive state engine keeping track of user selections.

CUJ Walkthrough

When a user opens the app, the AppShell launches and triggers the LiveDashboardPage. The RealtimeSubscriptionService initiates an SSE stream to fetch and update live listings. As matches update, changes are stored in the normalized ScoreQueryManager. When the user clicks a MatchCard, the ActiveMatchStore updates, instructing the MatchDetailContainer to subscribe to the granular game channel and immediately render historical events via the EventTimeline while streaming new statistical updates to the StatsTab.

Simplicity Audit

This architecture utilizes built-in browser capabilities (SSE) and basic, normalized client-side memory states. It avoids complex client-side database synchronization frameworks by employing a unidirectional data-flow design.

Architecture Decision Rationale

Why SSE over WebSockets for MVP?: Unidirectional updates dominate this scoreboard application; clients rarely push real-time data to servers. SSE handles automatic reconnections, connection multiplexing, and simple HTTP/2 fallback natively.
Why Normalize Client State?: High-frequency updates can cause massive re-renders if states are deeply nested. A flat, normalized state cache ensures that updating a single score only triggers a re-render on the affected MatchCard.
---

System Diagram

---
Architecture Deep Dive

Presentation Layer

Component Hierarchy

The UI flow moves strictly downward to prevent rendering side-effects:
AppShell: Mounts navigation headers, handles initial auth checks, and coordinates global responsive side drawers.
ScoreboardLayout: Responsive grid providing CSS Grid/Flex setups that scale elegantly from small smartphone viewports to wide desktop monitors.
LiveDashboardPage: Holds context-free configurations, filter rules, and layout logic.
MatchListContainer & MatchDetailContainer: State-connected containers subscribing to sliced portions of the global stores.
MatchCard, StatsTab, EventTimeline: Pure leaf components. They do not maintain global state and are highly optimized with strict memoization to prevent rendering except on prop mutation.

Interaction Layer

Input Validation & Filtering: Interactive sport and league filters are fully interactive and optimized using debounced transitions to avoid layout thrashing when users rapidly click multiple categories.
Accessibility (A11y):
Live components like scores and key game events utilize aria-live="polite" elements.
Interactive scoreboard tabs are fully compliant with WAI-ARIA authoring practices (using role="tab", aria-selected, aria-controls, and proper keyboard focus rings).
GPU Animations: Score flips and flashing yellow/red card events are executed via hardware-accelerated CSS transforms (translate3d, opacity, scale) to avoid layout reflows on the main thread.

Rendering Layer

Reconciliation & Memoization: Use React's React.memo (or equivalent in other frameworks) on MatchCard with custom comparative functions. It ensures cards only re-render if score, clock, or event sequence properties change.
Virtualization: Render potentially hundreds of ongoing world matches simultaneously using windowing (virtual list). Only matches currently visible in the user's viewport are mounted into the actual DOM tree.
Dynamic Batching: Real-time event ticks are grouped in 100ms intervals using dynamic UI update batching inside requestAnimationFrame hooks to prevent the UI from choking on rapid, sequential updates.

UI Frameworks / Tools

Tailwind CSS: For low-footprint, utility-first layouts that minimize bundle sizes.
Radix UI / Headless UI: For fully accessible, keyboard-navigable dialogs, tabs, and filter dropdowns.
---

Application Layer

Data Fetching Layer

Hybrid Fetching: High-level match schemas, list filters, and completed match histories are fetched using standard, cached HTTP GET queries with ScoreQueryManager.
Real-Time Subscription: Ongoing live events utilize the RealtimeSubscriptionService, subscribing to standard server-sent channels.
Resilience & Reconnection: Backed by a exponential-backoff retry scheduler. On disconnection, it re-establishes the connection with a Last-Event-ID header, requesting all missed event packages.

State Management Layer

Normalized Client Cache: The state is normalized to avoid duplicate nested references:
  interface ScoreboardState {
    matches: Record<string, MatchModel>;
    visibleMatchIds: string[];
    activeMatchId: string | null;
  }
ActiveMatchStore: Simple reactive subject/store containing layout states, current selected match IDs, and application preferences.

Routing & Navigation

URL-Based State: The selected match ID and active filter tags are mirrored directly to the URL route params (e.g., /sports/soccer/match/12345). This allows bookmarking, direct sharing, and preserves back/forward browser history seamlessly.
Guards: Lazy-loads detail views to ensure lightweight bundle sizes for dashboard-only viewers.
---

Domain Layer

Business Rules / Use Cases

Match Status Invariants: Coordinates state transitions (e.g., Scheduled -> First Half -> Halftime -> Second Half -> Full Time). Match clock logic is kept consistent regardless of system clock drifts on local client devices.
Score Computations: Aggregates aggregate scores for nested aggregate views, ensuring mathematical logic remains isolated from the layout framework.

Entities / Models

MatchModel: A pure domain class that encapsulates the raw state of a sports match, validation rules, period progress status, and provides clear methods to serialize dynamic API payloads.
EventParser: Class designed to transform external JSON stream payloads into reliable chronological entities with immutable, strongly typed representations.
export class MatchModel {
  readonly id: string;
  readonly homeTeam: string;
  readonly awayTeam: string;
  readonly homeScore: number;
  readonly awayScore: number;
  readonly status: 'scheduled' | 'live' | 'completed';
  readonly lastUpdated: number;

  constructor(data: any) {
    this.id = data.id;
    this.homeTeam = data.homeTeam;
    this.awayTeam = data.awayTeam;
    this.homeScore = Number(data.homeScore || 0);
    this.awayScore = Number(data.awayScore || 0);
    this.status = data.status;
    this.lastUpdated = data.lastUpdated;
  }

  get isLive(): boolean {
    return this.status === 'live';
  }

  get totalScoreString(): string {
    return `${this.homeScore} - ${this.awayScore}`;
  }
}

Inter-layer Contracts

The domain utilizes absolute separation of concerns. The components and applications depend only on the interfaces and abstract classes of the domain, adhering strictly to the Dependency Inversion Principle.
---

Infrastructure Layer

API / Network

Server-Sent Events (SSE): Standard stream connection established using the browser's native EventSource interface, transmitting plain-text events over HTTP/2.
API Client Isolation: Network requests are abstracted behind a unified service layer handling JWT tokens, timeouts, request deduplication, and cross-site request forgery (CSRF) headers.
export class RealtimeSubscriptionService {
  private eventSource: EventSource | null = null;

  subscribeToMatchStream(onMessage: (data: any) => void, onError: () => void) {
    this.eventSource = new EventSource('/api/v1/sports/live-stream', {
      withCredentials: true,
    });

    this.eventSource.onmessage = (event) => {
      const parsedData = JSON.parse(event.data);
      onMessage(parsedData);
    };

    this.eventSource.onerror = () => {
      this.eventSource?.close();
      onError();
    };
  }

  unsubscribe() {
    this.eventSource?.close();
  }
}

Storage

Session & In-Memory Storage: Temporary layouts, league preferences, and dynamic stream indexes are stored locally.
IndexedDB: Optional cache system used to store previous match timelines offline, letting users review older matches immediately without network activity.
---
Wrap Up

Wrap-up

Evaluation & Trade-offs

WebSocket vs SSE Trade-Off: WS is full-duplex, allowing client-to-server messaging. However, because our dashboard is read-intensive, SSE is chosen. This trade-off significantly simplifies load-balancing setups, scales through CDN layers, and features automatic client-side retries out of the box.
Local Clock Sync Trade-Off: Active live clocks could drift if simulated locally on clients. Instead of constantly hitting network endpoints for accurate clock times, the client syncs server timestamps once on boot, then computes elapsed times relative to high-resolution system performance marks (performance.now()). This reduces server clock sync requests to zero while maintaining absolute frame accuracy.

Advanced Optimization

To scale further, the application utilizes Content Security Policies (CSP) and subresource integrity (SRI) on real-time stream assets, preventing external frame injection attacks and guaranteeing a highly secured, fast-rendering UX.
---