Build a cricket scoreboard UI (LLD)
Domain: `Match`, `Innings`, `Over`, `Ball`, `Batter`, `Bowler`. Each ball updates running totals (runs, wickets, balls bowled) and triggers UI updates. State machine: ball-by-ball → over-completed → innings-end → match-end. Optimistic updates from a real-time feed (WebSocket); recompute derived stats (run rate, required rate) from primary ball events. Tests are easy because logic is pure.
A cricket scoreboard LLD is a great domain-modeling question: lots of natural objects, interesting state transitions, and pure derivations that make testing easy.
1. Domain model
Match
├── teams: [Team, Team]
├── format: T20 | ODI | Test
├── innings: [Innings, Innings] (or 4 for Tests)
└── currentInningsIndex
Innings
├── battingTeam, bowlingTeam
├── overs: Over[]
├── runs, wickets, balls, extras
├── batters: { [batterId]: BatterStats }
├── bowlers: { [bowlerId]: BowlerStats }
└── striker, nonStriker
Over
├── number, bowler
└── balls: Ball[]
Ball
├── runs (off bat)
├── extras: { wide, noBall, bye, legBye, penalty }
├── wicket?: { type, batterOut, fielder? }
├── isLegal: boolean (false for wide / no-ball)
└── striker, nonStriker (at this ball)
BatterStats { runs, balls, fours, sixes, isOut }
BowlerStats { overs, runs, wickets, dotBalls, maidens }2. State machine
Match: NotStarted → InProgress → Completed
Innings: NotStarted → InProgress → Ended (declared / all-out / overs-up / target)
Over: InProgress → Ended (6 legal balls)
Ball: pending → recorded (final)State transitions are triggered by:
- Recording a ball.
- Wicket falling → batter swap.
- Over end → bowler swap, strike change.
- Innings end → switch teams.
3. Recording a ball
function recordBall(innings, ball) {
const runs = ball.runs + sumExtras(ball.extras);
innings.runs += runs;
innings.extras += sumExtras(ball.extras);
if (ball.isLegal) innings.balls++;
if (ball.wicket) {
innings.wickets++;
innings.batters[ball.wicket.batterOut].isOut = true;
// ... fall-of-wicket entry
}
// batter stats
const striker = innings.batters[innings.striker];
if (ball.isLegal) striker.balls++;
striker.runs += ball.runs;
if (ball.runs === 4) striker.fours++;
if (ball.runs === 6) striker.sixes++;
// bowler stats
const bowler = innings.bowlers[currentBowlerId];
bowler.runs += runs;
if (ball.isLegal) bowler.balls++; // overs computed from balls
if (ball.wicket && ball.wicket.type !== "runOut") bowler.wickets++;
if (ball.runs === 0 && ball.isLegal && !ball.wicket) bowler.dotBalls++;
// strike change (odd runs off bat, or end of over)
if (ball.runs % 2 === 1) swapStrike(innings);
if (innings.balls % 6 === 0 && ball.isLegal) {
swapStrike(innings);
// trigger new over, new bowler
}
}Every ball is a small set of pure mutations. Easy to unit test.
4. Derived stats (computed, never stored)
- Run rate =
runs / overs(overs = legal balls / 6). - Required run rate =
(target - runs) / oversRemaining. - Partnership = sum of runs since last wicket.
- Powerplay status = derived from format + current over.
Compute these from primary data; never store them — they'd drift.
5. UI layout
┌─────────────────────────────────────┐
│ India 178/4 (16.3) vs Aus 162/8 │
│ Target: 163 (Need 28 in 3.3) │
├─────────────────────────────────────┤
│ Kohli* 62 (45) 4×5 6×3 │ ← striker (* marker)
│ Rohit 34 (28) │
├─────────────────────────────────────┤
│ Bumrah 3.3-0-22-1 │ ← current bowler
├─────────────────────────────────────┤
│ This over: 1 4 W 2 1 │ ← ball-by-ball
└─────────────────────────────────────┘- Big top: score, overs, target.
- Mid: current batters with bold strike marker.
- Bowler stats.
- Recent balls (last 6 or current over) — keys, dots, boundaries, wicket.
- Optional: live updates pulse on the changed cells.
6. Real-time updates
- WebSocket feed of ball events.
- Optimistic: append to local model; rollback if server rejects.
- Show "Live" indicator; reconnect on drop.
- Replay missed balls by event id since last seen.
7. Animations
- Pulse the changed score on update (subtle).
- Wicket triggers a brief overlay ("WICKET!").
- Boundary highlights the ball.
- Respect
prefers-reduced-motion.
8. Edge cases
- Free hit after a no-ball.
- Retired hurt — batter not out but no longer batting.
- DRS review pending — show "Under review" pill.
- Rain delays / D/L — derived target changes.
- Super over — handle extra-innings logic.
9. Accessibility
- Score updates announced via a polite live region ("KOHLI 4 — India 182 for 4").
- Don't rely on color for boundaries — text/icons.
- Keyboard: tab through batters/bowlers for stats panels.
10. Testing
Because the core (recordBall, derive run rate) is pure, write unit tests for:
- Specific ball patterns (wide + boundary, no-ball + 4 → 5 + free hit next).
- Strike-change rules.
- Over completion.
- Innings-end conditions per format.
Interview framing
"Core domain is Match → Innings[] → Over[] → Ball[], with running stats on batters and bowlers. Recording a ball is a small set of pure mutations: increment innings runs, update batter stats, update bowler stats, swap strike on odd runs or over-end, transition state on innings/match end. Derived stats — run rate, required rate, partnership — are computed, never stored. Real-time updates over WebSocket with optimistic appending and event-id-based catch-up on reconnect. UI: big score top, batters with strike marker, bowler, this-over ball-by-ball. Edge cases: free hit, retired hurt, DRS, D/L, super over. Because the logic is pure, unit testing is easy — that's the part to call out for senior signal."
Follow-up questions
- •Walk through recording a no-ball followed by a 4.
- •How do you handle a strike change?
- •Why compute run rate instead of storing it?
- •What's your strategy for sync on reconnect?
Common mistakes
- •Storing derived stats and drifting out of sync.
- •Not handling free hit / no-ball correctly.
- •Auto-updating UI without optimistic rollback.
- •Coloring boundaries/wickets without text/icon backup.
Performance considerations
- •Small data; perf is irrelevant. Memoize derived stats; subscribe to event stream by match id.
Edge cases
- •Free hit, retired hurt, DRS, D/L, super over.
- •Innings declared mid-over (Tests).
- •Run-out where bowler doesn't get the wicket credit.
Real-world examples
- •Cricinfo, Cricbuzz live scorecards.