SevyDevy

Guide · System design

Cricket Scoreboard LLD: Overs, Wickets and Live Updates in Java

28 min readUpdated By Saket Bhatnagar

Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.

In short

  • A good cricket scoreboard lld saves every delivery (one ball bowled) as a small record. Runs, overs, wickets and who is on strike are all worked out from that list.
  • Only legal balls count in an over. A wide (too far from the batter to hit) or a no ball (an illegal delivery) adds a one-run penalty and must be bowled again.
  • Fixed choices become Java enums, types with a fixed list of named values. Each value carries its own rule, such as whether a run out counts for the bowler (it does not).
  • Undo means remove and replay: drop the last ball and rebuild the innings (one side's turn to bat) from the rest. Live updates use the Observer pattern: after each ball, the code tells every listener that asked to hear.
  • Beginners can stop after the undo section. Engineers then get a tested bug where undo hides the fix from spectators, the 2022 rule on who faces after a catch, and free-hit wides.

What goes wrong when a cricket scoring app keeps only totals?

A scoring app that keeps only totals breaks the first time a ball is unusual. It adds to a few counters on every tap, and counters cannot explain or correct themselves. A cricket scoreboard LLD built on events fixes that.

Picture a common setup. A weekend tennis-ball tournament scores its 6-over matches on one phone, and spectators follow the score on their own phones. The setup is made up, but the bugs are the classic ones.

Version one of the app kept four values per innings: runs, wickets, balls and the striker's name. Every tap added to them. That worked while the games were mostly dot balls, singles and boundaries.

The semi-final had harder balls, and four things went wrong.

  • A wide was counted as a ball, so the 4th over ended after five legal balls. The batting side lost a delivery in a close chase.
  • A run out was added to the bowler's wickets. That bowler then won the best bowler prize by one wicket.
  • The scorer tapped 4 instead of 1. With no undo, the total was edited by hand, and the strike stayed with the wrong batter.
  • Spectators saw the score jump back by four runs when the scorer's phone lost signal and resent an old update.

All four bugs share one root. The app stored answers, not the events that produce the answers. The rest of this guide redesigns the app around that idea, one class at a time.

What is a cricket scoreboard LLD?

A cricket scoreboard LLD is the class-level design of a scoring app: which objects exist, what each one stores and how one ball changes them. LLD means low-level design, the zoom level where you name classes and methods.

High-level design asks which servers and databases you need. Low-level design asks what a Ball object holds and which method decides who faces next. Cricket makes a good LLD question because a small, familiar game hides many rules.

The backend that pushes scores to thousands of phones is a separate problem, covered in live score system design. This guide covers the scoring core on the scorer's phone, which decides what the score actually is.

Which cricket words do you need before the design?

You need seven words: innings, delivery, over, extra, wicket, strike and free hit. The rules described here come from the MCC (Marylebone Cricket Club) Laws of Cricket and the ICC (International Cricket Council) men's playing conditions for international matches.

These rules are revised from time to time. As of 19 October 2026, the MCC Laws are the 2017 Code, 4th edition, in force from 1 October 2026. The ICC editions are named in the sources.

Innings. An innings is one side's turn to bat. In a limited-overs match it ends when the overs run out, the chasing side reaches its target, or the side is all out. All out means only one batter is left not out.

Delivery. A delivery is one ball bowled by the bowler. Every tap on the scoring screen records exactly one delivery, legal or not.

Over. An over is six legal balls, bowled by one bowler from one end. MCC Law 17 says wides and no balls do not count toward the six. A 6-over match therefore has 36 legal balls per side.

Extra. An extra is a run not scored off the bat. A wide is a ball too far from the batter to hit with a normal shot. A no ball is an illegal delivery. A common cause is the front foot: no part of it lands behind the popping crease, a line painted in front of the stumps.

Byes and leg byes are the other two extras. A bye is a run taken when the ball touches neither bat nor batter. A leg bye is a run taken after the ball hits the batter's body, allowed only if the batter tried to hit it or get out of the way.

Wicket. Here a wicket means a batter getting out. The way they got out, such as caught or run out, is called the mode of dismissal.

Bowled means the delivery broke the stumps. Run out means a fielder broke the stumps while a batter was out of the crease, usually while running. Stumped means the wicketkeeper did it while the striker was out of the crease but not trying to run.

Hit wicket means the striker knocked over their own stumps. Hit the ball twice and obstructing the field (deliberately getting in a fielder's way) are rare, but the app must still know them.

Strike. The striker is the batter facing the bowler. The non-striker waits at the bowler's end. When the batters run an odd number of runs, they finish at opposite ends, so the striker changes.

Free hit. This rule comes from the ICC men's playing conditions for one-day internationals (ODIs, 50 overs a side) and T20 internationals (20 overs a side). There, the delivery after any no ball is a free hit. On a free hit the striker can be out only in the ways allowed off a no ball, which in practice mostly means run out.

What should a cricket scoring app do?

A cricket scoring app must record each delivery correctly, show the live score and keep player figures accurate. Charts, awards and highlights are all built on those three jobs.

Write the requirements down before any code. Functional requirements say what the app does. Non-functional requirements say how well it must do it, such as working with no signal.

  • Record a delivery with its runs, any extra and any wicket.
  • Count overs correctly when wides and no balls happen.
  • Track the striker, the non-striker and the bowler, and change strike at the right moments.
  • Keep team totals and bowler wickets, and refuse a wicket the rules do not allow on that ball.
  • Undo the last ball, several times in a row if needed.
  • Send live updates to spectators, and keep scoring when the ground has no signal.
  • Let each tournament set its own rules: overs, players per side, penalty runs, free hits and LBW.

The last requirement matters in tennis-ball cricket. The tournament in our setup plays without LBW (leg before wicket: roughly, out when the ball hits the batter's leg and would have gone on to hit the stumps). Judging it fairly needs a neutral umpire.

Out of scope for now: rain rules, player auctions and video. Saying this early keeps the design small, and interviewers like hearing a clear boundary.

What classes do you need to design a cricket scoreboard?

You need about seven classes: Match, MatchRules, ScoringSession, Innings, Ball, Wicket and Player. Ball is the most important, because every other number is computed from the list of balls.

Here is one analogy for the whole design. At a family carrom game, one uncle keeps score on the back of an old calendar sheet. He never writes a running total, only one line per turn.

When someone argues, he reads the lines aloud and adds them again. Each line is a Ball record. Adding them again is replay. Crossing out the last line is undo. The total is never the truth; the lines are.

Where the picture breaks: the uncle can fix any line in the middle, while the app only removes balls from the end. Middle edits are done by undoing back to that ball and recording the rest again.

Follow the arrows in the diagram from Match down to a single Ball. Notice that ScoringSession, not Innings, keeps the saved ball list. The engineer half shows why that split matters.

The scoring app's main classes

ScoringSession saves the ball list and rebuilds Innings from it. Innings only holds totals worked out from those balls.

Diagram connections:

  • Match to MatchRules (Uses)
  • Match to ScoringSession (Has one per innings)
  • ScoringSession to Ball (Saves every)
  • ScoringSession to Innings (Rebuilds)
  • Ball to Wicket (May have one)
  • Ball to Player (Names the bowler)
  • Match: Two teams, the toss and one session per innings
  • MatchRules: Overs, players, penalties, free hits, LBW
  • ScoringSession: Saves balls, rebuilds Innings, sends updates
  • Innings: Runs, wickets, legal balls, strike
  • Ball: Bowler, runs, extra and an optional wicket
  • Wicket: How out, who, fielder and new batter
  • Player: A name; holds no score of its own

An over is not a separate class with its own runs. It is a view over the ball list, worked out from the count of legal balls. Player holds no score either. A batter's runs also come from replay, from the balls they faced, once Ball records whether no-ball runs came off the bat.

Here are the building blocks in Java. Extra and Dismissal are enums. Wicket and Ball are records, short classes whose fields cannot change after creation. Records need Java 16 or newer.

java
enum Extra {
    NONE, WIDE, NO_BALL, BYE, LEG_BYE;

    boolean isLegal() { return this != WIDE && this != NO_BALL; }
}

enum Dismissal {
    BOWLED(true), CAUGHT(true), LBW(true), STUMPED(true), HIT_WICKET(true),
    RUN_OUT(false), HIT_TWICE(false), OBSTRUCTING(false);

    final boolean creditsBowler;
    Dismissal(boolean creditsBowler) { this.creditsBowler = creditsBowler; }
}

Two details carry the design. The isLegal() method lives on Extra, so no other class needs to remember that wides and no balls do not count. The creditsBowler flag lives on Dismissal, so a run out never reaches the bowler's figures.

java
// atStrikersEnd: the wicket fell at the striker's end, so the new batter
// takes that end (true for every mode except some run outs and obstructions)
record Wicket(Dismissal how, String batterOut, String fielder,
              String incoming, boolean atStrikersEnd) {}

// runs = runs the batters completed, or the boundary (4 or 6),
// or both added together when a throw at the stumps reaches the boundary
record Ball(String bowler, int runs, Extra extra, Wicket wicket) {}

Ball does not store the striker. Innings already knows who is on strike, so storing it twice would let the two copies disagree. The penalty run for a wide or no ball is not in runs either, because MatchRules decides it.

How do you count overs when wides and no balls happen?

Count only legal balls toward the over, and end it when the legal count reaches the balls-per-over setting. Wides and no balls add their penalty run but leave the count where it was.

This is MCC Law 17.3: a no ball or a wide is not a valid ball, and only valid balls count toward the six. Byes and leg byes are legal balls, so they do count.

Look at the two decision boxes in the next diagram. The first decides whether the ball uses up one of the six. The second decides whether the over is finished.

What happens to the over after each delivery

Only legal balls move the count. A wide or no ball adds its penalty run and the over carries on with the same bowler.

Diagram connections:

  • Scorer records a delivery to Is it a legal ball?
  • Is it a legal ball? to Count one legal ball (Yes)
  • Is it a legal ball? to Add the penalty run (No)
  • Count one legal ball to Six legal balls done?
  • Six legal balls done? to End the over (Yes)
  • Six legal balls done? to Wait for the next ball (No)
  • Add the penalty run to Wait for the next ball (Ball must be bowled again)
  • Scorer records a delivery: Runs, extra type and any wicket
  • Is it a legal ball?: Legal means not a wide and not a no ball
  • Count one legal ball: Byes and leg byes still count
  • Add the penalty run: One run by default, set in MatchRules
  • Six legal balls done?: Uses ballsPerOver from MatchRules
  • End the over: Swap strike and ask for a new bowler
  • Wait for the next ball: Same over, same bowler

The table sums up each kind of delivery under the ICC men's playing conditions. The app starts from these defaults and lets each tournament change the penalty runs.

How each kind of delivery changes the scoreboard under the ICC men's playing conditions
DeliveryCounts in the over?Penalty runsRuns the batters complete go toWays a batter can be out
Legal ballYes0The striker, if hit with the batAny mode the match allows
WideNo1Wides, charged to the bowlerStumped, hit wicket, run out, obstructing the field
No ballNo1The striker if hit with the bat, else byes or leg byesRun out, hit the ball twice, obstructing the field
ByeYes0Byes, added to the batting sideAny mode the match allows
Leg byeYes0Leg byes, added to the batting sideAny mode the match allows

How strike changes after a ball

Strike follows two rules. If the batters complete an odd number of runs, they end at opposite ends, so the striker changes. At the end of every over it changes again, because the next over comes from the other end.

A boundary that was not an overthrow sends the batters back to their original ends (MCC Law 18.11). A four or a six is even, so the odd-runs rule already leaves strike alone.

If the batters had already run more than the boundary is worth, those runs count instead and also decide the ends (Law 19.7.4), so the odd-runs rule still works.

An overthrow is a throw towards the stumps, to stop runs or try a run out, that runs away for extra runs (MCC Law 19.8, 2026 edition). If it reaches the boundary, the four is added to the runs already completed. One run plus four is five, which is odd, and the batters have indeed crossed once.

A misfield (a fielder fumbling a ball while trying to stop it) that crosses the boundary is not an overthrow under the 2026 edition. It is scored like any boundary, so 1 run plus a misfielded four is just 4, and the batters return to their original ends.

Since 1 October 2026 the fielding side also chooses the striker in a few rare cases, collected in MCC Law 18.13. Give the scorer a manual strike swap for those.

Pause and predict: the batters run one on a wide. Who faces next? The striker changes, because they ran an odd number. The side gets 2 wides, the penalty plus the run, and the over count does not move.

How do you record wickets and who gets credit for them?

Record a wicket as part of the ball it fell on, with the mode of dismissal, the batter who is out and any fielder. The mode decides whether the bowler gets credit.

A bowler's figures are the runs they conceded and the wickets they took. Bowled, caught, LBW, stumped and hit wicket go into the bowler's figures. Run out, hit the ball twice and obstructing the field do not; the MCC Laws for each say the bowler does not get credit. That is why creditsBowler sits on the enum.

Store the batter who is out on purpose. In a run out, the non-striker can be the one out. The rule is the batter whose ground is at the end where the wicket is broken (Law 38.4).

Which wickets are allowed on this ball?

Some modes are impossible on certain balls, and the app should refuse them. Off a wide, a batter can only be stumped, hit wicket, run out or out for obstructing the field (ICC clause 22.9).

Off a no ball, only run out, hit the ball twice or obstructing the field are possible (clause 21.18). On a free hit, the striker can be out only in the no-ball ways, even if the free hit is itself a wide (clause 21.19.2).

That last rule surprises people. A striker who is stumped off a wide on a free hit is not out, because stumping is not one of the no-ball ways.

java
record MatchRules(int oversPerSide, int ballsPerOver, int playersPerSide,
                  int widePenalty, int noBallPenalty,
                  boolean freeHits, boolean lbwAllowed) {

    static final Set<Dismissal> OFF_NO_BALL =
        EnumSet.of(Dismissal.HIT_TWICE, Dismissal.RUN_OUT, Dismissal.OBSTRUCTING);
    static final Set<Dismissal> OFF_WIDE = EnumSet.of(Dismissal.STUMPED,
        Dismissal.HIT_WICKET, Dismissal.RUN_OUT, Dismissal.OBSTRUCTING);

    int penaltyFor(Extra extra) {
        return switch (extra) {
            case WIDE -> widePenalty;
            case NO_BALL -> noBallPenalty;
            default -> 0;
        };
    }

    boolean canBeOut(Ball ball, boolean freeHit) {
        Dismissal how = ball.wicket().how();
        if (how == Dismissal.LBW && !lbwAllowed) return false;
        Set<Dismissal> allowed = switch (ball.extra()) {
            case WIDE -> EnumSet.copyOf(OFF_WIDE);
            case NO_BALL -> EnumSet.copyOf(OFF_NO_BALL);
            default -> EnumSet.allOf(Dismissal.class);
        };
        if (freeHit) allowed.retainAll(OFF_NO_BALL); // a free hit limits even a wide
        return allowed.contains(how);
    }
}

EnumSet is Java's compact set for enum values. canBeOut starts from the modes the extra allows, then narrows them on a free hit. The copy matters: retainAll changes the set it is called on, so narrowing the shared constant would break every later ball.

Recording a ball in Java

This is the record method on Innings. It checks the wicket first, then updates runs, wickets, strike, the legal-ball count and the free hit flag, in that order.

java
void record(Ball ball) {
    if (isOver()) throw new IllegalStateException("Innings is over");
    Wicket w = ball.wicket();
    if (w != null && !rules.canBeOut(ball, freeHitNext)) {
        throw new IllegalArgumentException("Batter cannot be out this way here");
    }
    if (w != null && !w.batterOut().equals(striker) && !w.batterOut().equals(nonStriker)) {
        throw new IllegalArgumentException(w.batterOut() + " is not batting");
    }
    runs += ball.runs() + rules.penaltyFor(ball.extra());
    if (w != null) {
        wickets++;
        if (w.how().creditsBowler) bowlerWickets.merge(ball.bowler(), 1, Integer::sum);
        bringInNewBatter(w);
    } else if (ball.runs() % 2 == 1) {
        swapStrike(); // odd runs: the batters ended at opposite ends
    }
    if (ball.extra().isLegal()) {
        legalBalls++;
        if (legalBalls % rules.ballsPerOver() == 0) swapStrike(); // over ends
    }
    freeHitNext = rules.freeHits() && (ball.extra() == Extra.NO_BALL
            || (freeHitNext && ball.extra() == Extra.WIDE));
}

The helper bringInNewBatter is shown in the section on who faces after a wicket. Both checks throw before anything changes, so a bad wicket never touches the totals. The legal-ball block is the only place the over count moves. The last line carries both free hit rules: a no ball starts one, and a wide during one keeps it.

Order matters here. Our first draft checked the batter's name later, after runs were added, and a test showed a refused wicket still counted. Validate everything first, then change state.

How do you undo a wrong ball in a scoring app?

Undo a ball by removing it from the saved list and rebuilding the innings from the balls that remain. Never subtract by hand, because one ball can change runs, strike, overs, wickets and the free hit at once.

This is called replay. Each Ball works like a command, a small object that describes one change. Saving the commands and replaying them is close to event sourcing, where the history of events is the saved data.

Follow the boxes from the Undo tap to the correction. Notice that the old Innings is thrown away, not patched.

How the app undoes the last ball

Undo rebuilds Innings from the ball list, then sends one correction with a higher sequence number (a counter that goes up with every update) than any update before it.

Diagram connections:

  • Scorer taps Undo to Drop the last ball
  • Drop the last ball to Start a fresh Innings
  • Start a fresh Innings to Replay remaining balls
  • Replay remaining balls to Send one correction
  • Scorer taps Undo: Last ball was a 4 that should be 1
  • Drop the last ball: The saved list now has one ball fewer
  • Start a fresh Innings: Same rules, same opening batters
  • Replay remaining balls: Each one goes through record(), quietly
  • Send one correction: Next sequence number, full scoreboard

Replay is fast. A 50-over innings has 300 legal balls plus extras. On our test sandbox, undo on a 326-delivery innings took about 10 to 75 microseconds (millionths of a second) across repeated runs on Java 17 and 21.

Replay also fixes the semi-final strike bug. When the scorer undoes the wrong 4 and records a 1, the 1 runs through the normal rules and the strike changes as it should.

Beginners can stop here. You now have the core design: a saved ball list, rules in one object, totals rebuilt by replay. The rest is for engineers who want the parts that break in real apps.

Why must the sequence number live outside the innings?

The sequence number must survive undo, so it cannot live on the object that undo throws away. If it does, the correction goes out with an old number and spectators ignore it.

A sequence number is a counter that goes up by one with every update the scorer sends. Spectators' phones keep the update with the highest number, so a late, older update can never replace a newer one.

Here is the naive design, labelled naive on purpose. Innings keeps the sequence and calls the listeners inside record(). Undo builds a fresh Innings and replays every remaining ball through record().

Pause and predict: ten balls are recorded, the tenth a wrong 4. The scorer undoes it and records a 1. What do spectators see?

We ran it. The fresh Innings restarted its count at 1 and sent nine old scores again while replaying. The fixed score finally went out as number 10, the same number as the wrong 4.

The spectator screen kept the first update numbered 10, so it showed the wrong 4 forever. In our run, the naive design sent 20 updates for 12 taps (11 balls and one undo), and the fix never appeared.

The fix moves the saved list, the sequence and the listeners into ScoringSession. Innings becomes pure totals. Replay runs quietly, and only the final result is sent.

java
final class ScoringSession {
    private final String matchId, opener1, opener2;
    private final MatchRules rules;
    private final List<Ball> balls = new ArrayList<>(); // the saved truth
    private final List<ScoreListener> listeners = new ArrayList<>();
    private Innings innings;
    private long sequence; // lives here, so a rebuilt Innings never resets it

    ScoringSession(String matchId, MatchRules rules, String opener1, String opener2) {
        this.matchId = matchId; this.rules = rules;
        this.opener1 = opener1; this.opener2 = opener2;
        this.innings = new Innings(rules, opener1, opener2);
    }

    void addListener(ScoreListener listener) { listeners.add(listener); }

    void record(Ball ball) {
        innings.record(ball); // throws before anything is saved
        balls.add(ball);
        publish();
    }

    void undo() {
        if (balls.isEmpty()) return;
        balls.remove(balls.size() - 1);
        Innings fresh = new Innings(rules, opener1, opener2);
        for (Ball b : balls) fresh.record(b); // replay quietly: no updates sent
        innings = fresh;
        publish(); // one correction, with a higher sequence number
    }

    private void publish() {
        ScoreUpdate update = innings.snapshot(matchId, ++sequence);
        listeners.forEach(listener -> listener.onUpdate(update));
    }

    // small getters follow in the full file
}

In the test, four ball taps and one undo sent exactly five updates, numbered 1 to 5. The spectator ended on update 5 with the corrected score.

How does a cricket scoreboard send live score updates?

The scoreboard sends live updates by calling every registered listener after each ball. This is the Observer pattern: one object keeps a list of listeners and calls each one when something changes, without knowing what they do.

One listener redraws the scorer's screen. Another writes the update to an outbox, a small queue stored on the phone that keeps updates until the server confirms them.

In the diagram, start at ScoringSession. Each arrow labelled onUpdate is one listener. Only the outbox talks to the network.

Live updates from the scoring phone

ScoringSession tells its listeners. Only the outbox talks to the server, and it resends in order after a signal drop.

Diagram connections:

  • ScoringSession to Scorer's screen (onUpdate)
  • ScoringSession to Commentary maker (onUpdate)
  • ScoringSession to Outbox on the phone (onUpdate)
  • Outbox on the phone to Live score server (Send in order)
  • Live score server to Spectators' phones (Push)
  • ScoringSession: Records a ball and builds one update
  • Scorer's screen: Redraws the scoreboard at once
  • Commentary maker: Turns each ball into a short line
  • Outbox on the phone: Keeps updates until the server confirms
  • Live score server: Keeps the highest sequence per match
  • Spectators' phones: Show only the newest sequence

What goes inside an update?

Each ScoreUpdate carries the match id, the sequence number and the full scoreboard, not just the change. A phone that missed three updates is fixed by the next one, because each update stands on its own.

java
final class SpectatorScreen implements ScoreListener {
    private ScoreUpdate shown;

    public void onUpdate(ScoreUpdate update) {
        if (shown == null || update.sequence() > shown.sequence()) shown = update;
        // an older or repeated update is simply ignored
    }

    ScoreUpdate shown() { return shown; }
}

That is the fix for the score jumping back. The resent old update has a lower sequence number, so spectators' phones drop it.

What happens when the ground has no signal?

The outbox stores updates on the phone and sends them in order when the signal returns. The server keeps the highest sequence it has seen per match and ignores anything not higher.

That makes each send idempotent: sending it twice has the same effect as sending it once. In our test, five updates plus two resends reached spectators exactly five times.

Can two people score the same match?

Not at the same time. The app allows one scoring phone per match, a single writer, and every other phone opens the match read-only. Two writers would need rules for merging two ball lists, which cricket scoring does not need.

If the scorer's battery dies, the organiser hands scoring to another phone with a short code. The new phone downloads the ball list and the last sequence number, then carries on from the next one.

Who faces the next ball after a wicket?

After most wickets the new batter takes the striker's end. After a run out, the new batter takes the end where the wicket was broken. Then the normal end-of-over swap applies.

The catch rule changed in 2022. Since 1 October 2022, MCC Law 18.11 and the ICC playing conditions say the new batter after a catch takes the striker's end. That holds whether or not the batters crossed.

Code written for the older rule checks whether the batters crossed before the catch. That check was correct before October 2022, and it now puts the wrong batter on strike.

The design records one fact to cover every case: did the wicket fall at the striker's end? For bowled, caught, LBW, stumped and hit wicket, the answer is always yes.

java
private void bringInNewBatter(Wicket w) {
    String notOut = w.batterOut().equals(striker) ? nonStriker : striker;
    if (wickets == rules.playersPerSide() - 1 || w.incoming() == null) {
        allOut = true;
        return;
    }
    if (w.atStrikersEnd()) { striker = w.incoming(); nonStriker = notOut; }
    else { striker = notOut; nonStriker = w.incoming(); }
}

We tested the over-end case. A catch on the sixth legal ball puts the new batter at the striker's end, then the over ends, so the not-out batter faces the next over. That matches the Laws' unless-it-is-the-end-of-an-over exception.

Which edge cases does a simple ball list miss?

A ball list covers almost everything, but a few events happen without a delivery, and one fact is missing from our Ball. Name these in an interview before the interviewer does.

Edge cases the sample design does not cover yet, and where each one would go
Edge caseWhat the rules sayWhere it goes in the design
Timed outMCC Law 40: a new batter not ready within 3 minutes of a wicket is out, with no credit to the bowlerA non-delivery event in the saved list
RetiringMCC Law 25.4: a batter may retire only while the ball is dead, recorded as retired not out or retired outA non-delivery event that frees a batting slot
Five-run penaltiesSome offences, such as unfair play under Law 41, cost 5 penalty runs, which can go to either sideA penalty event with the side it goes to
Fielders choose the strikerAfter a run cut short on purpose (ICC since mid-2025; MCC Law 18.5.2 from 1 October 2026) or an obstruction that stopped a catch (MCC Law 37.5.2)A manual strike swap, saved as its own event
Runs on a no ballOff the bat they go to the striker, otherwise they are byes or leg byesOne more Ball field: did the runs come off the bat?
Bowler limitsNo bowler may bowl two overs in a row (Law 17.6); ICC ODI (one-day international) conditions, December 2023 edition, cap a bowler at 10 oversA check in MatchRules before each new over

Each fix keeps the same shape: add an event or a field, teach record() one more rule, and replay still rebuilds everything. That is the payoff of storing events.

Check your own version with one over

Use this over as your first unit test, with free hits on and one-run penalties. Ball by ball: 1 run, a wide, a four, a no ball, 2 runs on the free hit, a dot ball.

Then the batters complete 1 run, cross on the second, and the non-striker is run out at the bowler's end. Next, a 4 is tapped by mistake, undone and recorded as 1.

Expected result: 11 runs for 1 wicket in 1.0 overs, which means one over and no extra balls. The scorer has sent 10 updates, and the opener who was on strike before the run out faces the next over.

Our implementation printed exactly that on Java 17 and 21, along with 43 passing rule tests. If yours differs, check the wide and no ball first: they add runs but not legal balls.

How do you explain a cricket scoreboard LLD in a system design interview?

Explain it as a saved ball list plus one rules object, then show how overs, wickets, undo and live updates all follow from that one choice.

  1. Clarify the rules

    Ask about overs, players per side, penalty runs, free hits and LBW. Say the rules will live in one MatchRules object.

  2. Name the core classes

    List Match, MatchRules, ScoringSession, Innings, Ball, Wicket and Player, with one line on what each stores.

  3. Make the ball the source of truth

    Say the app saves only the ball list. Runs, overs, strike and figures are computed from it.

  4. Handle extras and overs

    Show the isLegal check, the legal-ball counter and the two strike rules. Mention that the umpire's count overrides the app.

  5. Handle wickets

    Store the mode, the batter out, the fielder and the new batter. Show creditsBowler, and refuse modes the ball does not allow, including free-hit wides.

  6. Add undo

    Remove the last ball and replay the rest quietly. Keep the sequence number outside the rebuilt Innings.

  7. Add live updates

    Use the Observer pattern with a full scoreboard and a sequence number in each update. Name the outbox for grounds with no signal.

  8. Discuss extensions

    Mention timed out, penalty runs, bowler limits and super overs, and say which class each one touches.

Interviewers often push on one corner. Common follow-ups are a no ball that becomes a run out, a catch on the last ball of an over, and an umpire who allowed a seventh ball. The first two are deliveries with fields, so the ball list handles them. The seventh ball needs the umpire's-count override from the overs section, which the sample code leaves out.

Questions people ask

Should a cricket scoreboard store totals or every ball?
Store every ball. Totals like runs, overs and bowler wickets can always be rebuilt from the ball list, but a ball list cannot be rebuilt from totals. Many apps also keep the latest totals in memory for speed, which is fine as long as the ball list stays the saved truth.
Does a wide count as a ball in an over?
No. MCC Law 17.3 says a wide is not a valid ball, and only valid balls count toward the six. The same is true for a no ball. In the code, only deliveries where isLegal() is true move the over count, and both still add their penalty run.
How do you handle a no ball that is also a run out?
Record one Ball with extra set to NO_BALL, the runs completed before the run out, and a Wicket with mode RUN_OUT. The rules check allows it, because run out is possible off a no ball. The penalty run is still added, the completed runs count, and the over count does not move.
Can a batter be out caught on a free hit?
No. Under the ICC men's playing conditions, a striker on a free hit can be out only in the no-ball ways. Those are run out, hit the ball twice and obstructing the field. That holds even when the free hit delivery is a wide, so stumping is not out either.
Who faces the next ball after a batter is caught?
The new batter, unless the catch was on the last ball of the over. Since 1 October 2022, the MCC Laws and ICC playing conditions put the new batter at the striker's end after a catch, whether or not the batters crossed. At the end of an over the usual swap then applies.
Is a cricket scoreboard LLD the same as a live score system design?
No, they are two layers of the same product. The LLD covers classes on the scorer's phone: balls, overs, wickets and rules. The live score system design covers servers that push each update to many phones. The ScoreUpdate with its sequence number is where the two meet.
Why use Java records for a ball?
Records fit because a recorded ball never changes. A Java record is a short class whose fields are private and final, and Java generates the constructor, accessors (one method to read each field), equals, hashCode and toString. Records need Java 16 or newer. If a ball was wrong, you undo it and record a new one.

Sources and further reading

Practise this with Lala

Practise