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
| Quantity | Assumption | Result |
|---|---|---|
| Events | 10B/day | ~115,000/s average, ~300,000/s peak |
| Distinct items per hour | Hundreds of millions | Exact per-item counters per window are expensive |
| Count-min sketch | e.g. 4 rows × 2M counters × 4 bytes | ~32 MB per window per aggregator |
| Reads | Top-K page views | Easily 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
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.
ZINCRBYto add points,ZREVRANGE 0 99for the top 100,ZREVRANKfor 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
- 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.
- What if K changes at query time? Store a deeper list (top 1,000) and slice.
- 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.
- 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.
- 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.