When an interviewer asks you to design a leaderboard, they are probing how you balance real‑time responsiveness with massive scale. The problem looks simple – keep a list of users and their scores – but the devil is in the details: handling frequent updates, serving low‑latency top‑k queries, and ensuring durability across failures. Below is a practical walkthrough you can use in a live interview.
1. Clarify the Scope
Start by asking clarifying questions. You want to know:
- Domain – Is it a gaming leaderboard, a coding challenge site, or a sales performance board?
- Operations mix – How many writes per second versus reads? Are we serving a "top 10" view constantly, or do users also request arbitrary rank look‑ups?
- Latency expectations – Do we need sub‑second latency for reads? Is eventual consistency acceptable for writes?
- Data durability – Must every score be persisted immediately, or can we tolerate brief loss during a crash?
- Geography – Are users worldwide, requiring multi‑region replication?
These questions let you tailor the design and avoid over‑engineering.
2. Functional and Non‑Functional Requirements
Functional
- Add / Update Score –
POST /score {userId, delta}orPUT /score {userId, newScore}. - Get User Rank –
GET /rank/{userId}returns the current rank and score. - Get Top‑K –
GET /top?k=10returns the highest‑scoring users. - Reset Leaderboard – optional admin operation to clear scores.
Non‑Functional
| Requirement | Typical Target | Why it matters |
|---|---|---|
| Throughput | thousands of writes per second, millions of reads per second | Leaderboards are read‑heavy; writes must not become a bottleneck. |
| Latency | < 100 ms for reads, < 200 ms for writes | Users expect instant feedback, especially in games. |
| Scalability | Horizontal scaling on commodity hardware | Traffic can spike during events. |
| Availability | 99.9 %+ uptime, graceful degradation on partitions | Service should stay usable even if a shard fails. |
| Consistency | Strong for reads of a single user’s rank, eventual for global ranking | Users care about their own position, less about exact ordering across the board. |
3. Core Data Model
A minimal schema:
CREATE TABLE scores (
user_id VARCHAR PRIMARY KEY,
score BIGINT NOT NULL,
updated_at TIMESTAMP NOT NULL
);
scoreis a monotonic integer (e.g., points earned).updated_athelps resolve ties and supports time‑based queries.
For ranking, we need a sorted structure. Two common choices are:
- Sorted Set in Redis – O(log N) insert, O(log N) rank lookup, O(k log N) top‑k.
- B‑tree index in a relational DB – slower for massive writes but provides durability.
In practice, a hybrid approach works best: persistent storage for durability and an in‑memory sorted set for fast reads.
4. High‑Level Architecture
+----------------+ +----------------+ +-------------------+
| Client UI | <----> | API Layer | <----> | Write Service |
+----------------+ +----------------+ +-------------------+
| |
v v
+----------------+ +-------------------+
| Cache (Redis) | | Persistent Store |
+----------------+ +-------------------+
^ ^
| |
+----------------+ +-------------------+
| Read Service | | Backup / Replay |
+----------------+ +-------------------+
Components
- API Layer – Handles HTTP/GRPC endpoints, validates input, forwards to services.
- Write Service – Applies score deltas, updates Redis sorted set (write‑through) and persists to the database.
- Read Service – Serves
GET /topandGET /rankusing Redis for low latency; falls back to DB if cache miss. - Cache (Redis) – Holds the sorted set
leaderboard:{gameId}and a hashscore:{userId}. - Persistent Store – Relational DB or a distributed key‑value store; stores the authoritative score.
- Backup / Replay – Streams write logs to a durable sink (e.g., object storage) for recovery.
5. Deep Dive: Handling Hot Keys
A leaderboard often suffers from a hot key problem: the top‑ranked user’s score is updated far more frequently than others. If every update writes to the same Redis node, you create a bottleneck.
Strategies
- Sharding by Score Range – Split the sorted set into multiple shards (e.g., 0‑1 M, 1 M‑10 M). Updates to a user stay within its shard, reducing contention.
- Write‑back Queue – Buffer updates in a lightweight queue (Kafka, Pulsar) and batch‑apply them to Redis. This smooths spikes.
- Hybrid Counter – Keep a per‑user delta in a fast in‑memory map; flush to Redis every few seconds. This trades a slight delay for higher throughput.
6. Trade‑offs and Alternatives
| Option | Pros | Cons |
|---|---|---|
| Pure Redis (sorted set only) | Ultra‑fast reads, simple code | No durability; loss on restart unless persisted snapshots are used. |
| DB‑only with B‑tree index | Strong durability, ACID guarantees | Write latency higher; scaling reads requires read replicas. |
| Hybrid (Redis + DB) | Best of both worlds: fast reads, durable writes | More moving parts; needs cache‑invalidation logic. |
| Time‑windowed leaderboards | Enables rolling‑period rankings (daily, weekly) | Requires additional aggregation logic. |
Choosing depends on the interviewer's constraints. If they stress durability, lean toward the hybrid model. If they care about sub‑millisecond latency for a small user base, a pure Redis solution may be acceptable.
7. Typical Follow‑Up Questions
- How would you handle ties? – Use
updated_atas a secondary sort key; store a composite score(score, timestamp)in the sorted set. - What if we need to support multiple leaderboards (e.g., per region)? – Prefix keys with
leaderboard:{region}and shard each region independently. - How do you ensure consistency between cache and DB? – Adopt a write‑through pattern: write to DB first, then update Redis. On failure, roll back or retry.
- Can you add a “friends only” view? – Store adjacency lists in a separate cache; compute top‑k by intersecting the friend set with the global leaderboard.
- What if the write rate spikes 10× during a tournament? – Scale the write service horizontally, increase queue partitions, and optionally enable a burst‑mode that relaxes consistency for a few seconds.
8. Sample Answer (45‑90 seconds)
"Sure, I’d start by confirming the expected traffic pattern – typically leaderboards are read‑heavy, with occasional bursts of writes during events. I’d model each score as a row in a durable store and keep a Redis sorted set as a cache for fast ranking. The API would expose three endpoints:
POST /scoreto add a delta,GET /rank/{userId}for a user’s current position, andGET /top?k=for the top‑k list. Writes go through a write‑through service that updates the DB and then the Redis set, ensuring durability. Reads first hit Redis; on a miss we fall back to the DB. To avoid hot‑key contention on the top users, I’d shard the sorted set by score range and batch updates via a lightweight queue. This design gives sub‑100 ms read latency, scales horizontally, and tolerates failures through log replay."
You can rehearse this flow with Call Assistant, which will listen to your answer, keep you on track, and suggest concise phrasing based on your resume.
9. How to Practice This
- Sketch the architecture on paper before the interview, labeling each component’s responsibility.
- Run a mock interview with a peer, focusing on clarifying questions and trade‑off discussions.
- Use Call Assistant to practice delivering the answer aloud; it will highlight where you drift and help you anchor stories in your own experience.
FAQ
- Q: What is the simplest way to implement a leaderboard for a small app?
A: Use a single Redis sorted set with
ZADD,ZRANK, andZREVRANGE. Persist snapshots periodically if durability is needed. - Q: How do I guarantee that a user’s rank is always accurate after a score update? A: Perform the update in a transaction that writes to the DB and then updates the Redis sorted set. If the cache update fails, retry until it succeeds.
- Q: When should I consider a time‑windowed leaderboard? A: If the product shows daily or weekly rankings, aggregate scores into buckets and reset them at the period boundary, keeping the raw scores for historical analysis.
- Q: Can I avoid a separate read service? A: For very small scales, the API layer can directly query Redis and fall back to the DB, but separating concerns makes scaling and testing easier.
Frequently asked questions
What is the simplest way to implement a leaderboard for a small app?
Use a single Redis sorted set with ZADD for updates, ZRANK to fetch a user’s position, and ZREVRANGE for the top‑k list. Persist snapshots periodically if you need durability.
How do I guarantee that a user’s rank is always accurate after a score update?
Wrap the DB write and the Redis sorted‑set update in a transaction. If the cache update fails, retry until it succeeds, ensuring the cache reflects the authoritative store.
When should I consider a time‑windowed leaderboard?
If the product displays daily, weekly, or monthly rankings, aggregate scores into time buckets and reset them at the period boundary while keeping raw scores for longer‑term analysis.
Can I avoid a separate read service?
For very small traffic you can let the API layer query Redis directly and fall back to the DB on a miss, but a dedicated read service simplifies scaling and testing as the system grows.
#system design#leaderboard#scalability#caching#interview prep#a leaderboard