Code a live cricket scoreboard
Open in the interactive app: practice, quiz, mock interview →In one minute
- A cricket scores site is a projection engine. The source of truth is an append-only log of deliveries, and the scorecard (team total, overs, each batter's runs and balls, each bowler's figures) is derived from it.
- Cricket's scoring rules are the hard part, not the storage. A wide is not a legal ball and is not faced by the batter. A no-ball is not legal but is faced. Byes count for the team but not the batter or the bowler. Encode each delivery kind as a rule object, not as a growing
ifchain. - Corrections are normal. A scorer mis-keys a run, or a review overturns a dismissal. Undo removes the last event and rebuilds the projection by replaying the log, so every derived number is exact.
- Strike rotation is a small state machine: odd runs swap the batters, the end of an over swaps them, and a dismissal brings a new batter in at the dismissed batter's end.
- The problem shape is ordering (deliveries must be applied in sequence, and a correction rewrites history) plus fan-out (one ball updates millions of open pages). Order books and bank ledgers have the same shape: an ordered event log, with balances or books as projections.
Requirements
Functional
- Set up an innings with a batting order and an over limit.
- A scorer starts each over with a bowler and records each delivery: its kind (
run,wide,no_ball,bye,leg_bye), the runs taken, and an optional dismissal (bowled,caught,lbw,run_out). - Show the team score as
runs/wickets, overs asO.B, each batter's runs and balls faced, each bowler's overs, runs conceded and wickets, extras, and who is on strike. - Enforce the basic laws: six legal balls per over, and no bowler bowls two overs in a row.
- End the innings when the batting side is all out or the overs run out.
- Undo the last recorded event.
Non-functional
- The scorecard is always exactly the result of replaying the stored deliveries.
- Readers see each ball within a second or two of the scorer entering it.
- A correction never leaves the scorecard in a state that no sequence of deliveries could produce.
Out of scope
- Pushing updates to millions of clients, which Design Live Comments and Real-time Updates cover. Also out of scope: match and series management, multi-innings Test matches, DLS targets, penalty runs, and a run out of the non-striker. We note where each plugs in.
Entities and relationships
Match has two Teams, a format (T20, ODI, Test) and a list of Innings. Innings is the aggregate this design is about. It owns the batting order, the over limit, and an append-only list of Event objects: OverStarted(bowler) or a Delivery (kind, runs, optional Dismissal, plus commentary text and a timestamp in production).
Scorecard is the projection, a plain data object rebuilt from the events. It holds the total, wickets, extras, legal balls bowled, the striker and non-striker, the next batter in, a BattingEntry per batter (runs, balls, how out), and a BowlingFigures per bowler (legal balls, runs conceded, wickets).
DeliveryRule is one strategy per delivery kind. It knows whether the ball is legal, whether the batter faced it, who gets the runs, and what is charged to the bowler. Dismissal knows whether the bowler is credited: bowled, caught and lbw are, run out is not.
ScoringService is the facade the scorer's app calls. It validates an event against the current scorecard, appends it, applies it, and publishes a BallRecorded event.
Class diagram
classDiagram
class Innings {
+battingOrder list
+maxOvers int
+events list~Event~
+startOver(bowler)
+ball(kind, runs, dismissal)
+undo()
}
class Event {
<<interface>>
+validate(card)
+apply(card)
}
class OverStarted {
+bowler
}
class Delivery {
+kind
+runs int
+dismissal
}
class DeliveryRule {
<<interface>>
+isLegal() bool
+facedByBatter() bool
+batterRuns(runs) int
+extras(runs) int
+bowlerCharge(runs) int
}
class WideRule
class NoBallRule
class ByeRule
class RunRule
class Scorecard {
+total int
+wickets int
+legalBalls int
+striker
+nonStriker
+batting map
+bowling map
}
class ScoringService {
+record(event)
+undo()
+subscribe(listener)
}
Event <|.. OverStarted
Event <|.. Delivery
Delivery --> DeliveryRule
DeliveryRule <|.. WideRule
DeliveryRule <|.. NoBallRule
DeliveryRule <|.. ByeRule
DeliveryRule <|.. RunRule
Innings "1" *-- "*" Event
Innings --> Scorecard
ScoringService --> InningsKey flows
The interesting flow is a correction. The umpire gives Ana out caught, the scorer records it, and a review then overturns it. The scorer presses undo. The service drops the last event and replays the rest into a fresh scorecard, so Ana's balls faced, the wicket count, the bowler's wickets and the strike all return to exactly where they were.
sequenceDiagram
participant S as Scorer app
participant SV as ScoringService
participant I as Innings
participant C as Scorecard
participant L as Listeners
S->>SV: ball(run, 0, caught)
SV->>I: validate against card
I->>C: apply: wickets 1, Cal on strike
SV->>L: BallRecorded(3.4)
S->>SV: undo()
SV->>I: drop last event
I->>C: rebuild by replaying 22 events
C-->>I: wickets 0, Ana on strike
SV->>L: BallCorrected(3.4)
S->>SV: ball(run, 0)
SV->>L: BallRecorded(3.4)Listeners receive a correction as its own event. A commentary feed that already printed "OUT! Ana c Ben b Xi" appends a correction line instead of silently changing history, which is how live commentary is written by hand anyway.
Design patterns used and why
Event sourcing. The innings stores what happened, not the totals. Totals are a fold over events. This makes undo trivial and exact, gives a free audit trail for disputes, and lets you rebuild the scorecard after a rules bug is fixed. Replaying a full T20 innings is about 150 events, which takes microseconds.
Strategy for delivery kinds. WideRule, NoBallRule, ByeRule (with a leg-bye twin) and RunRule each answer the same five questions: legal? faced? runs to the batter? extras? charge to the bowler? Adding a free hit after a no-ball, or a penalty-run event, is a new rule rather than another branch in a 60-line apply method.
Command. Each scorer action is an object with validate and apply. Undo uses replay rather than an inverse per command, because inverses are easy to get wrong when a wicket also changed who is on strike.
State for the over. An over is not_started or in_progress; ball is valid only in progress, start_over only when not started, and six legal balls move it back.
Observer. Commentary, push alerts ("WICKET!"), the win predictor and the statistics database subscribe to BallRecorded and BallCorrected. The scoring core never waits on them.
Concurrency and edge cases
One writer per innings
Scoring is a single-writer problem: one official scorer per match, sometimes with a backup. Give each innings a sequence number and make the client send expected_seq with every event, like a compare-and-set. A second device, or a retry of a request that actually succeeded, gets a conflict instead of recording the same ball twice. In memory, a mutex around validate, append and apply is enough.
Retries and duplicates
A scorer on stadium Wi-Fi taps "4" and the request times out. The retry carries the same (innings, seq); the server sees an identical event already stored there and returns success without appending, the same idempotency-key idea used for payments.
The rules that trip people up
A wide adds one run plus anything the batters run, all as extras. It is not a legal ball and the batter has not faced it. A no-ball adds one extra, and any runs off the bat go to the batter. It is not legal, but it counts as a ball faced. Byes and leg byes are legal and faced, but go to extras and are not charged to the bowler. Since the 1980s, wides and no-balls have been charged to the bowler. A run out is not credited to the bowler. Under the 2022 laws, a new batter comes in on strike after a catch whether or not the batters crossed, which matches "replace the dismissed striker".
Ends of innings and older corrections
The innings ends when the side is all out (wickets equal batters minus one) or the final over's last legal ball is bowled; both checks run after every delivery. To fix a ball three overs ago, apply "replace event at sequence n" and replay from there with the same projection code.
Down the stack
Layer 1The contract
The scorecard equals fold(apply, empty_scorecard, events) for the stored events, in order. Each event was valid against the scorecard produced by the events before it. undo yields exactly the scorecard of the shorter log. Overs shown as O.B count only legal balls. A batter's balls include no-balls but not wides. Bowler runs include wides and no-balls but not byes or leg byes. A rejected event changes nothing and is not stored.
Layer 2The mechanism
Each delivery is applied in O(1): look up the rule, update a handful of integers in two maps, maybe swap the strike, maybe advance the next-batter index. Undo is O(n) in the innings' events: about 150 for a T20, a few hundred for an ODI, and over 1,000 for a long Test innings. If that matters, snapshot every over and replay from the latest snapshot, which is how event-sourced systems bound rebuild time. In a database the log is a table with primary key (innings_id, seq), enforcing one event per position, and the scorecard is a cached row.
Layer 3The machine
A delivery event is tiny: kind, runs and dismissal fit in a byte or two, and with ids and a timestamp it is about 32 bytes. A whole ODI innings is around 20 KB, which fits in the L1 data cache of any modern CPU, so a full replay is a few microseconds of integer arithmetic. The expensive part is everything after the write. An update packet with the new score and a commentary line is roughly 200 to 500 bytes. At 10 million connected readers during a big match, one ball means 2 to 5 GB of outbound data, delivered within seconds and repeated about every 30 to 40 seconds. That is why such sites push deltas through CDNs and fan-out tiers rather than serving each reader from the scoring database.
Why it's built this way
Scorers make mistakes and reviews overturn decisions, so the system must undo exactly, so it stores events and derives totals. Cricket's rules give each delivery kind different effects on five counters, so each kind is a strategy and the projection code stays readable. An innings has a single official scorer, so a sequence number and a mutex give total order without distributed consensus. Reads outnumber writes by millions to one, so the write path is a small in-memory projection and the read path is a fan-out of small deltas.
Extensibility
- Free hits. A
NoBallRulesets afree_hit_nextflag on the scorecard. The next delivery's dismissal validation rejects everything except run out. - Penalty runs. A
PenaltyAwarded(team, 5)event with its own rule. It touches extras and the total, and nothing else. - Partnerships, fall of wickets and wagon wheels. More projections over the same log, with no new write path.
- Multiple innings and targets. A
Matchaggregate owns innings and computes targets; DLS is a strategy reading overs and wickets from each scorecard.
Leadership lens
- Insist that every number on the page is reproducible from the event log, with a nightly job that replays all of today's innings and compares them with the cached scorecards. Drift means a bug in the incremental path.
- Put the cricket rules in one module owned by someone who knows the laws, with a test table of ball sequences and expected scorecards taken from real matches. That table is worth more than any class diagram.
- Budget for the read side, not the write side. Writes are one per 30 seconds per match; reads during a World Cup final are in the millions per second. Most of the cost and on-call load is in fan-out.
- Plan the scorer's offline mode early. Stadium connectivity fails, and a client queue with sequence numbers and idempotent retries is far cheaper than reconciling two divergent scorecards afterwards.
Check yourself
Over 7 starts. Xi bowls a wide that the batters run for one, then a no-ball hit for four. What are the overs, the extras added, the batter's balls faced, and the runs charged to Xi?
Show answer
Overs stay at 6.0, because neither ball is legal. Extras rise by 3: 2 from the wide and 1 from the no-ball. The batter who hit the four gets 4 runs and 1 ball faced, from the no-ball only. Xi is charged 7 runs.
Why rebuild the scorecard by replay on undo instead of applying an inverse of the last delivery?
Show answer
A delivery can change many things at once: a wicket, who is on strike, the end of an over, the end of the innings. Writing a correct inverse for every combination is error prone. Replaying a short log is cheap and correct by construction.
The batters take three byes off the fifth ball of an over. Who faces the sixth ball, and who faces the first ball of the next over?
Show answer
Three is odd, so the non-striker is now on strike for ball six. If ball six is a dot, the end-of-over swap puts the batter who faced ball five back on strike for the first ball of the next over, bowled from the other end.
Two scorer devices both submit ball 4.3, one of them a retry. How does the system make sure the ball is recorded once?
Show answer
Each event carries the expected sequence number. The first one to arrive is appended. An identical retry is acknowledged without appending, and a different event at the same position is rejected as a conflict.
Watch
- CricInfo/Cricbuzz Low Level Design (The Code Mate): an object model for matches, innings, overs and balls.
- Design Cricket Info Backend, Machine Coding (Balasubramanian Bangalore): the problem as a machine-coding round, implemented in Java.
- CricBuzz and ESPNcricinfo System Design (designKarle): the distributed side, from data providers to millions of live readers.
Free resources
- The Laws of Cricket (MCC): the official laws for overs, no-balls, wides, byes and dismissals that the rules engine encodes.
- Extra (cricket) (Wikipedia)): a compact summary of each kind of extra and whom it is charged to.
- Scoring (cricket) (Wikipedia)): how scorers record balls, extras and dismissals, which is the event log this design stores.
- Cricinfo (Wikipedia): the site's history, from a 1993 project by Simon King to ESPN's acquisition in 2007.
- Event Sourcing (Martin Fowler): the pattern behind deriving the scorecard from the delivery log and rebuilding it on correction.
- Command pattern (Refactoring.Guru): scorer actions as objects that can be validated, logged and replayed.