← Back to Blog
Engineering11 min readMarch 25, 2026

Building a Real-Time Fantasy Cricket Engine — Behind the Scenes

When 50,000 users are watching their fantasy scores update live, a lot of invisible infrastructure is running. Here's how we built the CricFuntasy scoring engine from the ground up.

When a cricket match is live and tens of thousands of users are watching their fantasy points update ball by ball, a lot of invisible infrastructure is doing heavy lifting. The user sees a number tick up when their player hits a six. What they don't see is a scoring engine ingesting a live event stream, computing point deltas across thousands of teams, updating a distributed leaderboard, and pushing changes to clients — all within a few seconds of the actual delivery.

At CricFuntasy, we built this system from scratch. Here's what the real engineering problem looks like, and the architectural decisions we made along the way.

The Core Problem: Event-Driven Scoring at Scale

Cricket scoring is not continuous — it is event-based. A ball is bowled. Something happens: dot ball, boundary, wicket, wide, no-ball, leg bye. Each event triggers score updates for potentially dozens of active players across tens of thousands of fantasy teams simultaneously.

The naive approach — recalculate every team's total score from scratch on every ball — breaks immediately at any meaningful scale. Recalculating 50,000 teams every 30 seconds during a live match is a non-starter. You need an event-sourced model where you process the delta (the incremental change from each event) and propagate it efficiently through the system.

Score Calculation as a Pure Function

The scoring engine's most important design property is that it must be a pure function. No side effects, no database calls inside the calculation logic, no external state dependencies:

calculatePoints(event, playerState, scoringRules) => pointsDelta

Given this ball event and the current player state, what is the points change? That is the entire function signature. This design gives you three critical properties: testability (you can unit test every edge case in isolation), determinism (same inputs always produce the same output), and auditability (log every event and recalculate if a score is ever disputed).

When a user claims their score is wrong, you replay the event log through the same function and verify. There is no ambiguity. The math is transparent.

Player State Machines

Each player in a live match exists in a finite state machine. The states matter because they determine which scoring rules apply:

  • Yet to bat — waiting for batting order
  • Currently batting — accumulating batting points
  • Dismissed — batting session closed, final batting points locked
  • Currently bowling — accumulating bowling points per over
  • Fielding — available for catch, stumping, run-out credits

State transitions trigger different logic. A batter who opens and then bowls later in the innings needs separate batting and bowling accumulators running concurrently. A player dismissed without facing a ball (run out backing up) needs special handling — their duck penalty applies but strike rate modifier does not. A substitute fielder who takes a catch has different point rules than a regular playing XI member.

Every one of these edge cases needs to be modeled explicitly. The scoring engine that handles the clean scenarios takes a weekend to build. The one that handles all the edge cases takes weeks.

The Leaderboard Problem

Updating a ranked leaderboard in real time for 50,000 teams when points change every ball is a classic distributed systems problem. There are three main approaches, each with different trade-offs:

  • Full sort on every update: O(n log n) per ball, completely unscalable at this team count
  • Sorted sets in Redis (ZADD): O(log n) per update, O(log n + k) for rank reads — fast and accurate
  • Approximate rankings with periodic reconciliation: fast live view, exact result computed once at match end

We use Redis sorted sets for live leaderboard updates during the match. Every time a team's score changes, we update its position in the sorted set with O(log n) cost. Users see near-real-time rank changes. At the end of the match, we run a full reconciliation pass to verify final rankings before prize allocation — because the final result must be exact, even if the live view has millisecond-level approximation.

Handling Edge Cases: Where Real Engines Are Built

Cricket has edge cases that expose every weak assumption in your scoring engine. These are not rare events — they happen in most international matches:

  • Duckworth-Lewis-Stern (DLS) method: does a rain-shortened match change how we calculate economy rate modifiers?
  • Overthrows: who gets credit for extra runs — the batter or nobody?
  • Penalty runs for misconduct: how are they attributed in fantasy?
  • Rain delays mid-match with team lock already active: match resumes, but the context has changed
  • Wrong team announcement: a player announced in the XI did not actually play — how do you handle teams built on that information?
  • Retired hurt: does a batter who retires hurt get a duck penalty?

Each of these needs an explicit rule in your engine. A scoring system without answers to these questions will produce inconsistent results at the worst possible moments — during high-stakes live matches when user trust is most fragile.

Client Updates: Why We Chose SSE Over WebSockets

For pushing live score updates to users' browsers, we evaluated WebSockets and Server-Sent Events (SSE). The key insight: during a live match, fantasy users don't send data — they only receive updates. This is a fundamentally unidirectional communication pattern.

SSE is simpler to implement, reconnects automatically on dropped connections, works over standard HTTP/2 multiplexing, and requires no special infrastructure beyond a standard web server. WebSockets make sense when you need bidirectional real-time communication. For a live score feed, SSE is the right tool, and its simplicity means fewer failure modes in production.

The Full Stack at a Glance

  • Score ingestion: WebSocket connection to live cricket data provider
  • Event processing: queue-based worker pool — decouples ingestion speed from processing speed
  • Scoring engine: stateless TypeScript functions, pure and testable
  • Storage: PostgreSQL for match data and team composition, Redis for live leaderboards
  • Client delivery: Server-Sent Events from Next.js API routes to browser
  • Audit log: every event stored immutably for replay and dispute resolution

Every architectural decision here comes back to the same question: does this make the experience faster, fairer, and more trustworthy for the user? The scoring engine is the core trust mechanism of a fantasy platform. If users don't trust that their points are calculated correctly, everything else is irrelevant.

More from MindFifth

Deep dives into fantasy cricket strategy, platform engineering, and the psychology of competitive play.

Browse All Articles