System design guide

Design a top-K system and real-time leaderboard

"Top K most viewed videos", "trending hashtags", and "game leaderboard" are the same family of problem: count a firehose of events and keep the largest few always ready to read. The interview tests whether you know when to count exactly, when to approximate, and how to merge partial results.

1. Clarify the requirements

Two very different systems hide behind this prompt, so ask which one:

  • Heavy hitters / trending: the top K items by event count over a time window (most-played songs in the last hour, trending hashtags). Huge numbers of distinct items, approximate answers acceptable.
  • Leaderboard: every player has a score; show the top N and any player's exact rank. Fewer, known entities; exact answers expected.

This guide designs the heavy-hitters system and then covers leaderboards, since interviewers often ask both.

Functional requirements

  • Ingest events (e.g. "video X viewed").
  • Return the top K items for windows of 1 minute, 1 hour, and 1 day.
  • For leaderboards: update a player's score; return top N and a given player's rank.

Non-functional requirements

  • Freshness: results lag events by seconds to a minute.
  • Low-latency reads: top-K queries are served from precomputed results.
  • Accuracy: approximate for trending; exact for daily reports and leaderboards.

2. Estimates

QuantityAssumptionResult
Events10B/day~115,000/s average, ~300,000/s peak
Distinct items per hourHundreds of millionsExact per-item counters per window are expensive
Count-min sketche.g. 4 rows × 2M counters × 4 bytes~32 MB per window per aggregator
ReadsTop-K page viewsEasily served from a cache

The write rate needs partitioned stream processing; memory per window must be bounded, which is what sketches give you.

3. High-level architecture

Top-K architecture Event producers write to a partitioned event log. Stream aggregators, one per partition, count events per time window and keep a local top-K, sending partial results to a merger that combines them and writes the global top-K to an in-memory store every few seconds. The event log is also archived to a data lake, where a batch job computes exact counts and overwrites the approximate results. A query API reads from the top-K store. archive partial every few s overwrite read Event producers Event log(partitioned) Stream aggregators(per partition) Top-K merger Data lake Batch job(exact counts) Top-K store(in memory) Query API
Fast approximate path through stream aggregators; slow exact path through batch; one serving store.

Partitioned aggregation. Partition the event log by item ID so all events for one item land on the same aggregator. Each aggregator keeps counts per window (exact counters for items it sees, or a count-min sketch plus a min-heap of its local top candidates) and periodically emits its local top K. Because partitions are disjoint by item, the global top K is simply the top K of the union of local top Ks.

Windows. Use tumbling windows (e.g. one-minute buckets) and compute an hour or day by summing buckets. Keep the last 60 minute-buckets per aggregator to answer "last hour" as a sliding window.

4. Data model

events        item_id, timestamp, (user_id, region, ...)       -- log, partitioned by item_id
topk:{window}:{bucket}   sorted set: member = item_id, score = count  (top 1,000 kept)
exact_counts  item_id, day, count                               -- warehouse table from batch

leaderboard:{game}:{season}   sorted set: member = player_id, score = points

Keep more than K (for example 1,000 when K is 100) in each serving list, so items just below the cut-off aren't lost when partial results merge.

5. Leaderboards

  • Small to medium (up to tens of millions of players): one sorted set per leaderboard. ZINCRBY to add points, ZREVRANGE 0 99 for the top 100, ZREVRANK for a player's rank, all logarithmic.
  • Very large: shard by score range so each shard holds a band of scores; a player's rank is their rank in their shard plus the sizes of all higher shards. Or give exact ranks for the top few thousand and percentile ranks ("top 3%") for everyone else.
  • Durability: the sorted set is a serving structure; the source of truth is a score table (or the event log), so the leaderboard can be rebuilt.
  • Ties: break ties by who reached the score first, encoding time into the score's low-order digits.

6. Scaling

  • Hot items: a viral item sends all its events to one partition. Pre-aggregate on producers (count locally for a second, send one increment with a count) or salt hot keys across a few partitions and combine later.
  • Backpressure: if aggregators fall behind, results get staler but stay correct; monitor consumer lag.
  • Multiple dimensions (top K per country, per category): each is another keyed aggregation; limit the combinations you precompute and compute rare ones from the batch tables on demand.

Monitoring

Watch consumer lag on the event log (how stale results are), the gap between streaming and batch results (how much the approximation is off, which tells you whether sketch sizes are right), and read latency on the serving store. Alert if a window's top list stops changing, which usually means an aggregator is stuck rather than that the world got boring.

7. Trade-offs to call out

  • Exact vs approximate: sketches bound memory and error; exact counting is simpler but memory grows with distinct items.
  • Freshness vs accuracy: the stream path is fresh, the batch path is exact; serving both (often called a lambda architecture) costs two pipelines.
  • Window granularity: finer buckets give smoother sliding windows but more state.

8. Common follow-up questions

  1. How do you compute "trending" rather than "most popular"? Score by growth: compare the current window's count with a baseline (e.g. the same hour last week), with smoothing so tiny items don't top the list.
  2. What if K changes at query time? Store a deeper list (top 1,000) and slice.
  3. How do you handle late events? Allow a grace period per window (event time, not arrival time), then let the batch job correct anything later.
  4. How would you de-duplicate repeated views from one user? Keep a short-lived set or HyperLogLog per item per window to count distinct users instead of raw events.
  5. Where does caching fit? The top-K store is effectively the cache; put a CDN or short TTL cache in front of the API for the most common queries.

FAQ

What is a count-min sketch?

A fixed-size table of counters with several hash functions. Each event increments one counter per row; an item's estimated count is the minimum across its counters. It never undercounts, overcounts by a bounded amount, and uses a tiny, constant amount of memory regardless of how many distinct items there are.

How do you build a real-time game leaderboard?

Store scores in a sorted set (for example Redis ZADD/ZINCRBY) keyed by leaderboard. Top-N is a range read, and a player's rank is a single rank lookup, both logarithmic time. Shard very large leaderboards by score range or use approximate ranks for players outside the top.

Why combine batch and streaming for top-K?

Streaming gives fresh but approximate results; a batch job over the raw event log gives exact results later. Serving the streaming answer and replacing it with the batch answer when available gets you both freshness and accuracy.