Redis Sorted Sets and Ranked Data
- redis
- database
- sorted-sets
- leaderboards
- backend
- data-structures
If you learn one Redis structure deeply, make it the sorted set. It's a set where every member carries a numeric score, and members stay ordered by that score. That one addition (a score) unlocks a remarkable range of problems: leaderboards, priority queues, time-ordered feeds, sliding-window rate limiters, and range queries by value. A large share of "how do I do X in Redis" questions have "use a sorted set" as the answer, so it's worth understanding well.
This is part 7 of the Redis Masterclass, following sets.
Members with scores, kept in order
A sorted set (zset) stores unique members, each with a score, and maintains them sorted by score:
ZADD leaderboard 1500 "alice" 1800 "bob" 1200 "carol"
ZSCORE leaderboard "bob" # 1800
ZINCRBY leaderboard 50 "alice" # alice's score += 50, reorders automatically
Adding or updating a score re-positions the member automatically, so the set is always in order. That's the magic: you never sort anything, because Redis keeps the ordering maintained as scores change. Reads of the top, the bottom, or a rank are then instant.
Leaderboards: the flagship use
A leaderboard is a sorted set where the score is points. Getting the top N, a player's rank, or a range is built in:
ZREVRANGE leaderboard 0 9 WITHSCORES # top 10, highest first
ZREVRANK leaderboard "alice" # alice's 0-based rank from the top
ZRANK leaderboard "carol" # rank from the bottom
ZREVRANGE 0 9 returns the top 10 in one command, already ordered. ZREVRANK gives a member's position. Both are fast regardless of how many members the set has, which is what makes sorted sets the standard leaderboard implementation. Doing this in a relational database means an ORDER BY and a rank computation on every request; in Redis the ranking is always maintained and the read is immediate.
Range queries by score
Because members are ordered by score, you can query ranges of scores directly. This is powerful when the score is meaningful, like a price, an age, or a timestamp:
ZRANGEBYSCORE products 10 50 # members with score (price) 10 to 50
ZCOUNT products 10 50 # how many in that range
ZRANGEBYSCORE events 0 1714500000 # everything up to a timestamp
Using a timestamp as the score turns a sorted set into a time-ordered index: add events with their epoch time as the score, and range queries fetch everything in a time window, in order. This is the basis of time-series feeds and, as we'll see, sliding-window rate limiters.
Priority queues
A sorted set makes a clean priority queue: the score is the priority, and you always take the lowest (or highest) scored member. Peek and pop are atomic:
ZADD tasks 1 "urgent-job" 5 "normal-job" 10 "low-job"
ZPOPMIN tasks # take the highest priority (lowest score)
ZPOPMIN and ZPOPMAX atomically remove and return the extreme member, so competing workers each pull the next-priority task without stepping on each other. When work has priorities rather than plain FIFO order, a sorted-set priority queue is the natural fit, and it stays ordered automatically as new tasks arrive with their priorities.
Sliding-window rate limiting
One of the most elegant sorted-set patterns is a precise sliding-window rate limiter. Store each request as a member scored by its timestamp, drop anything older than the window, and count what remains:
# for a 60-second window, on each request:
ZREMRANGEBYSCORE rate:user:1 0 (now - 60000) # drop old entries
ZADD rate:user:1 now "request-id" # record this request
ZCARD rate:user:1 # how many in the window
If ZCARD exceeds the limit, reject. Unlike a fixed-window counter, this gives a true rolling window with no burst at window boundaries. We'll build it fully (wrapped in a Lua script for atomicity) in the rate-limiting article, but the sorted set is what makes the precise version possible.
Why sorted sets are worth mastering
Step back and the pattern is clear: any problem that involves ordering or ranking by a value maps onto a sorted set. Points to a leaderboard. Priority to a queue. Timestamp to a time-ordered feed or rate limiter. Price or size to a range query. In each case Redis keeps the structure sorted and answers top-N, rank, and range queries instantly, work that's expensive to do repeatedly in a general-purpose database.
The sorted set is the most versatile structure Redis offers, and recognizing when a problem is "really" a ranking or ordering problem is the skill that unlocks it. When you catch yourself wanting to sort data by some value and repeatedly ask for the top few or a member's position, reach for a sorted set.
Next, we finish the fundamentals module with key expiration, TTL, and eviction policies, the mechanics that make Redis safe to use as a cache without running out of memory.
Key takeaways
- A sorted set stores unique members each with a score and keeps them ordered by score automatically as scores change.
- It's the standard leaderboard: `ZREVRANGE` for top N and `ZREVRANK` for a member's rank, both fast at any size.
- Range-by-score queries (`ZRANGEBYSCORE`) make it a time-ordered index when the score is a timestamp.
- `ZPOPMIN`/`ZPOPMAX` give an atomic priority queue where the score is the priority.
- Scoring members by timestamp enables a precise sliding-window rate limiter, unlike a fixed-window counter.
Frequently asked questions
What is a Redis sorted set?
A collection of unique members, each with a numeric score, kept ordered by that score. Redis maintains the ordering automatically as scores change, so top-N, rank, and range-by-score queries are fast without any manual sorting.
How do I build a leaderboard in Redis?
Use a sorted set with the score as points: `ZADD leaderboard <points> <player>`. Read the top players with `ZREVRANGE leaderboard 0 9 WITHSCORES` and a player's rank with `ZREVRANK`. Updating a score with `ZINCRBY` reorders automatically.
How do sorted sets handle time-ordered data?
Use a timestamp as the score. Then `ZRANGEBYSCORE` fetches everything within a time window in order, turning the sorted set into a time-ordered index for feeds and analytics.
Can I build a priority queue with a sorted set?
Yes. Store tasks with their priority as the score, then use `ZPOPMIN` (or `ZPOPMAX`) to atomically remove and return the highest-priority task. Multiple workers can pop concurrently without conflict.
Why are sorted sets good for rate limiting?
Scoring each request by its timestamp lets you drop entries older than the window and count the rest, giving a true sliding window with no boundary burst, which a fixed-window counter can't provide.
Explore more on

About the author
I'm Aman Kumar Singh, a software engineer in Noida, India building scalable full-stack products with React, Next.js, Node.js, NestJS, PostgreSQL, Redis, and AWS. I write about backend engineering, distributed systems, and system design.