Articles
Redis Sorted Set Interview Questions: Leaderboards, Ties, and Sliding Windows
Practice Redis Sorted Set interview questions on tie ordering, rank policies, bounded leaderboards, score precision, and atomic sliding-window admission.
Two players have 100 points. A Redis leaderboard puts Carol ahead of Bob, even though Bob reached 100 first. The command is behaving correctly: the application asked Redis for score order, but never encoded an arrival-time tie rule.
Redis Sorted Set interview questions turn on that distinction. Redis orders unique members by score and a deterministic tie order; the application defines what a rank means, which history remains available, and which updates must happen together. The same structure can support a leaderboard or a sliding-window limiter, but the member and score contracts differ.
Evidence boundary: Official facts are linked to current Redis command documentation. The fixtures, algorithms, and design tradeoffs are original practice examples. No candidate reports establish interview frequency here. Local model checks validate arithmetic and ordering assumptions; this article does not claim a Redis deployment benchmark.
What is unique in a sorted set: the member or the score?
Official fact: Sorted-set members are unique, while multiple members can share a score. Updating an existing member changes its score rather than adding another occurrence. Redis sorted sets.
Original fixture: On an empty test key, run:
ZADD scores 90 alice 100 bob 100 carol
ZRANGE scores 0 -1 WITHSCORES
ZRANGE scores 0 -1 REV WITHSCORES
Official fact: ZRANGE defaults to ascending score order, with lexicographic member ordering for ties. REV reverses both the score order and the tie order. Rank endpoints are inclusive, so 0 9 requests ten positions. ZRANGE.
The ascending fixture order is Alice, Bob, Carol. The descending order is Carol, Bob, Alice. Adding Bob again with score 105 moves Bob ahead; it does not create a second Bob. Adding a second event with member bob would therefore be wrong if each event must remain independently countable.
Define the member accordingly. For a leaderboard, a stable player ID is usually the intended identity. For an event window, use a unique event or request ID. A timestamp makes a useful score but a poor sole member when two events can arrive in the same millisecond.
Does ZREVRANK implement the product's tie policy?
Official fact: ZREVRANK returns a zero-based position in descending order. A member absent from the set has no returned rank. ZREVRANK.
Worked comparison: For the initial fixture, three reasonable product policies produce different labels:
| Player | Score | Position plus one | Competition rank | Dense rank |
|---|---|---|---|---|
| Carol | 100 | 1 | 1 | 1 |
| Bob | 100 | 2 | 1 | 1 |
| Alice | 90 | 3 | 3 | 2 |
Redis's ordinal position is not competition rank. To compute competition rank for a known score, count members with strictly greater scores and add one. Dense rank instead counts distinct greater score values, which requires additional work or another maintained index.
Official fact: ZCOUNT supports exclusive score bounds using (. ZCOUNT. For this fixture, ZCOUNT scores (100 +inf returns zero, so both 100-point players receive competition rank 1 under the proposed formula.
Design inference: If the score lookup and the count must describe one instant, combine them in an atomic operation. Two separate reads can observe an intervening score change. Also handle a missing member before counting; absence should not silently become last place or rank one.
An earliest-achievement tie rule needs explicit data. Redis's default tie ordering does not preserve insertion time. Avoid presenting a player ID's alphabetical order as a fairness policy simply because it is deterministic.
How do ZADD options and floating-point scores affect updates?
Official fact: ZADD NX inserts only missing members; XX updates only existing members. GT allows an existing score to increase but still permits a missing member to be inserted. Scores use 64-bit floating point, with exact integer representation through ±2^53. ZADD.
Original policy examples: A personal-best leaderboard may accept ZADD scores GT 120 alice. A correction that lowers Alice to 110 would be ignored under that policy, so administrative corrections need a deliberate alternative path. If only registered participants may appear, GT alone is insufficient: adding an unknown player still succeeds.
For a cumulative score, an increment is a different contract:
ZINCRBY scores 5 alice
Official fact: ZINCRBY adds the supplied amount and creates a missing member with that score. ZINCRBY.
Design inference: Retrying the command after a lost response can add five twice. An absolute score update avoids that particular double increment but can overwrite a newer total unless the application also enforces ordering. Choose stable operation IDs or ordered snapshots according to the source data.
Packing points and a timestamp into one numeric score also deserves scrutiny. Multiplying a large points value by a large tie-break factor can exceed the exact-integer range. Specify maximum points, timestamp resolution, and tie-break width before claiming the packed representation is exact. Keeping an explicit secondary ordering structure may be easier to explain and maintain.
Can you keep only the top K players?
Official fact: ZREMRANGEBYRANK removes an inclusive rank interval; negative indexes count backward from the highest-scored end. ZREMRANGEBYRANK.
Original bounded-cache algorithm: For a positive fixed K, add or update the candidate, then remove the lowest N-K members if cardinality N exceeds K. For K = 3 and N = 5, remove ascending ranks 0 through 1. The remaining positions follow the same score-and-member tie order as the leaderboard query.
Make the update and trim one atomic operation if readers must never observe more than K members. Validate K before calculating indexes. An off-by-one error can remove the last member you intended to keep.
The harder question is what information trimming destroys. Start with A = 100, B = 90, C = 80, and D = 70. Keep only the top three, discarding D. If C later drops to 10, the retained set cannot discover that D should re-enter at 70. Deleting a leading player creates a similar gap.
Design inference: A bounded top-K set can be a cache backed by a complete score store. It can also work under carefully stated monotonic-update assumptions when incoming updates carry complete candidate scores. It cannot support arbitrary score decreases, deletions, and exact re-ranking from discarded state alone.
Be especially careful with deltas for previously trimmed members. Recreating D with an increment of five yields five, not the correct accumulated 75. The full total must come from retained authoritative state or the incoming event contract.
How does a sorted set implement an exact sliding window?
Original admission contract: Allow at most three accepted requests in the interval (now - 60,000, now], measured in milliseconds. Store each accepted request ID as a member and its admission timestamp as the score. Use a trusted, nondecreasing time source for this exercise; client-supplied future timestamps would violate the contract.
At now = 100,000, the cutoff is 40,000:
| Event timestamp | Keep or remove? | Reason |
|---|---|---|
| 39,999 | Remove | Older than the window |
| 40,000 | Remove | Left boundary is excluded |
| 40,001 | Keep | Inside the interval |
| 100,000 | Keep | Right boundary is included |
Official fact: ZREMRANGEBYSCORE includes both bounds unless explicitly made exclusive. Therefore removing scores from -inf through the cutoff implements this excluded-left-boundary policy. Its cost includes the number of removed members. ZREMRANGEBYSCORE.
A whole-key TTL reclaims inactive users' state. It does not remove old individual members while the key remains active. Pruning by score supplies the rolling-window semantics; expiration supplies eventual cleanup after inactivity.
If two requests arrive at timestamp 100,000, distinct request IDs preserve two members with equal scores. Using the timestamp as both score and member would collapse them into one and undercount traffic.
Why must prune, count, and add be atomic?
Original race: Two workers each observe two accepted requests in a limit-three window. Both decide there is room and add a new member. The result is four accepted requests. Every individual command succeeded; the compound admission rule failed.
Official fact: Redis Lua scripts execute without interleaving other server activity. Keep scripts short because execution occupies the server. Scripting with Lua.
This original sketch assumes a dedicated sorted-set key, validated numeric inputs, positive window and limit, and globally unique request IDs:
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
local request_id = ARGV[4]
redis.call('ZREMRANGEBYSCORE', key, '-inf', now - window)
if redis.call('ZSCORE', key, request_id) then
return {1, 'already_admitted'}
end
if redis.call('ZCARD', key) >= limit then
return {0, 'limit_reached'}
end
redis.call('ZADD', key, now, request_id)
redis.call('PEXPIRE', key, window)
return {1, 'admitted'}
Design limits: This remembers accepted IDs only while they remain in the window. It does not provide permanent idempotency, preserve rejected decisions, or prove that a downstream business action ran once. Reusing an admitted request ID must not let a caller execute that action repeatedly. Authenticate request identity and keep business-operation deduplication separate when needed.
The time assumption also matters. A clock moving backward can leave future-scored entries in the set; blindly counting them changes the stated interval. Define a clock policy and test it rather than treating milliseconds as automatically monotonic.
Because only accepted requests are inserted, retained cardinality is bounded by the limit under these assumptions. If a different design records every rejected attempt, memory grows with attempted traffic instead. That is a product and capacity decision, not just a different command order.
What does a bounded response cost?
Official fact: ZRANGE has a documented cost of O(log N + M), where M is the number of returned members. Score-range pagination with a large LIMIT offset can require traversing many skipped elements. ZRANGE complexity.
Design inference: Request the top ten directly rather than fetching every member and sorting in application code. Returning all scores creates network and client-memory work even when the server can locate the range efficiently. A command's logarithmic lookup component does not make an unbounded response cheap.
For a page around one player, find the player's position, clamp the surrounding rank interval to zero, and request that small interval. If the two operations must describe a consistent view, combine them atomically. Otherwise document that a concurrent score change can move the player between reads.
Offset pagination also lacks snapshot stability when rankings change. A player can appear twice across pages or move into an already-read page. An interview answer should state whether this is acceptable for a live leaderboard or whether the product needs a frozen ranking version for exports, prizes, or audit results.
Practice the ordering and admission boundaries
| Practice question | Sorted-set follow-up |
|---|---|
| Maintain an Escape-Room Leaderboard | Specify whether tied scores share a rank or only an ordering. |
| Find Nearby High Scores in a Large Game Leaderboard | Explain rank-window retrieval and concurrent score changes. |
| Design a dynamic leaderboard with rank queries | Compare complete state with a bounded top-K cache. |
| Implement sliding-window rate limiter function | Test exact cutoff equality and two requests at one timestamp. |
| Reason About Rate Limiting Under Memory and Throughput Pressure | Bound stored admissions and account for pruning cost. |
For a final rehearsal, predict the output before running any commands: reverse two equal-score members, lower a previously retained top-K member, and submit two distinct requests at the same millisecond. Then replay one accepted ID after its window entry has expired. Each case exposes a different assumption about ordering, retained information, or request identity.
Continue with the leaderboard system-design guide when you need to extend these local data-structure rules to a complete ranking service.
Sources and Further Reading
- Redis sorted sets: member and score semantics.
- ZADD: conditional updates and numeric precision.
- ZRANGE: ordering and retrieval.
- ZREVRANK: descending positions.
- ZCOUNT: score-range counts.
- ZINCRBY: incremental score updates.
- ZREMRANGEBYRANK: bounded rank removal.
- ZREMRANGEBYSCORE: window pruning.
- Scripting with Lua: atomic server execution.