Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

Add Scores and Range Queries

Master sorted set scores and range queries in Redis: adding scores, retrieving by rank or score range, and practical examples for real-world use.

Focus: add scores and range queries

Sponsored

Imagine you're building a live leaderboard for a gaming app. Players' scores change constantly, and you need to quickly fetch the top 10, or maybe all players with scores between 500 and 1000. Without a purpose-built data structure, you'd be stuck sorting and filtering in application code — slow, error-prone, and hard to scale. Redis sorted sets, with their score-driven mechanics, solve this elegantly: you add or update a member with a score, and the set stays sorted automatically. Then you query by rank or score with commands like ZADD, ZRANGE, and ZRANGEBYSCORE. In this lesson, you'll learn how to add scores and perform range queries to build real-time ranking systems, time-series summaries, and more.

The problem this lesson solves

Standard Redis sets are great for membership checks (SISMEMBER) and union/intersection, but they don't maintain any inherent order. When you need ordered data — like leaderboards, priority queues, or sorted activity feeds — you face a choice: sort in your app (costly and slow), use a sorted list (hard to maintain), or reach for sorted sets.

A sorted set in Redis is like a hash that also keeps members in order based on a numeric score. Each member must be unique; scores can be integers or floating-point numbers (up to double precision). The set is always sorted by score, ascending. This means:

  • Adding or updating a member with ZADD automatically repositions it.
  • You can query a slice of the sorted list by rank (e.g., top 5) or by score range (e.g., 80–100).
  • Operations are O(log(n)) for add/remove, and O(log(n)+m) for range queries — extremely efficient even with millions of members.

Previously you might have used a list and an external sort, or a plain set with application-side logic. Now you'll see why sorted sets are the go-to for any use case needing order and score-based filtering.

Core concept / mental model

Think of a sorted set as a combination of a hash and a skiplist. The hash ensures O(1) lookups by member name, while the skiplist (plus a hash table for scores) keeps members sorted by score. Every member has a score that determines its rank (position in the sorted order). If two members have the same score, they're ordered lexicographically by member string.

Pro tip: Scores are not unique — you can have multiple members with the same score. Redis handles ties by member name (binary comparison).

The basic flow: 1. ZADD key score member — adds or updates the score of a member. 2. ZRANGE key start stop — retrieves members by their rank position (0-based, ascending). 3. ZREVRANGE — same but descending (high to low). 4. ZRANGEBYSCORE key min max — retrieves members with scores between min and max. 5. ZREM key member — removes a member.

Key terminologies

  • Score: A double-precision number (floating point) that determines order.
  • Rank: The position of a member in the sorted set (0 = lowest score).
  • Querying: You can ask for members by rank range or by score range.
  • Lexicographic order: Tie-breaker when scores are equal.

How it works step by step

When you call ZADD, Redis performs an upsert: if the member doesn't exist, it inserts it with the given score; if it does, it updates the score and repositions the member in the skiplist. This is both an add and an update in one command.

To retrieve data: 1. By rank: ZRANGE gives you members from index start to stop (inclusive). Use WITHSCORES to also retrieve the scores. 2. By score: ZRANGEBYSCORE lets you specify min and max scores (inclusive by default). Use (inf for infinity, (5 for exclusive boundaries. 3. Count: ZCOUNT key min max returns the number of members in the score range. 4. Score retrieval: ZSCORE key member fetches the score of a single member. 5. Reverse order: ZREVRANGE and ZREVRANGEBYSCORE invert the order (descending).

Practical example

Suppose you have a game with player scores. You want to: - Add/update Alice, Bob, Carol with different scores. - Show the top 2 players (highest scores). - Show all players with scores between 100 and 450. - Get the rank of Bob.

The commands are straightforward; see the hands-on section below.

Hands-on walkthrough

Let's jump into redis-cli and work through a realistic scenario.

Example 1: Adding scores and querying by rank

# Connect to Redis (assumes redis-server running on localhost:6379)
redis-cli
# Add player scores
ZADD game:scores 300 Alice
ZADD game:scores 450 Bob
ZADD game:scores 200 Carol
ZADD game:scores 350 Dave
ZADD game:scores 500 Eve

# (integer) 1 for each new member

Now retrieve the top 3 players (highest scores):

# Descending order by rank: positions 0 to 2 (first 3)
ZREVRANGE game:scores 0 2

# 1) "Eve"
# 2) "Bob"
# 3) "Dave"

With scores:

ZREVRANGE game:scores 0 2 WITHSCORES

# 1) "Eve"
# 2) 500
# 3) "Bob"
# 4) 450
# 5) "Dave"
# 6) 350

Example 2: Query by score range

Get all players with scores between 200 and 400 (inclusive on both ends):

ZRANGEBYSCORE game:scores 200 400 WITHSCORES

# 1) "Carol"
# 2) 200
# 3) "Alice"
# 4) 300
# 5) "Dave"
# 6) 350

To get scores greater than 350 (exclusive) up to 500 (inclusive):

ZRANGEBYSCORE game:scores (350 500 WITHSCORES

# 1) "Bob"
# 2) 450
# 3) "Eve"
# 4) 500

Example 3: Count and score lookup

# How many players have a score <= 350?
ZCOUNT game:scores -inf 350
# (integer) 3  (Carol, Alice, Dave)

# What's Bob's current score?
ZSCORE game:scores Bob
# "450"

Example 4: Updating a score

# Alice wins bonus: her score goes from 300 to 600
ZADD game:scores 600 Alice

# Verify new ranking: Alice is now #1
ZREVRANGE game:scores 0 5 WITHSCORES
# 1) "Alice"
# 2) 600
# 3) "Eve"
# 4) 500
# 5) "Bob"
# 6) 450

Pro tip: ZADD is idempotent for updating — just run it with the new score. Redis automatically removes the old entry and inserts the new one in the correct position.

Compare options / when to choose what

Command Purpose When to use
ZRANGE Get members by rank range (ascending) Displaying paginated leaderboards from low to high
ZREVRANGE Get members by rank range (descending) Showing top N highest scores (most common)
ZRANGEBYSCORE Get members by score range Filtering scores (e.g., all orders above $50)
ZREVRANGEBYSCORE Get members by score range descending Reverse order score queries
ZCOUNT Count members in a score range Knowing how many items pass a threshold
ZSCORE Fetch score of one member Checking individual score (e.g., user's rating)
ZRANK / ZREVRANK Get index (rank) of a member Finding position for pagination or highlighting

Alternatives: - Lists: ordered by insertion/position, not score. Use when you need FIFO/LIFO, not score-based ordering. - Hashes: no inherent order. Use when you need to store field-value pairs by key and never need range queries. - Plain sets: good for membership but no ordering. Use for unique flags or tags.

For most real-time leaderboards (gaming, sales, forums), sorted sets with ZADD + ZREVRANGE are the canonical choice.

Troubleshooting & edge cases

  • Forgetting WITHSCORES — Without it, you only get member names, not their scores. Many beginners wonder why scores don't appear. Always add WITHSCORES if you need them.
  • Negative scores — Sorted sets allow negative scores (e.g., penalty values). They work fine but may surprise during sorting (lowest position = most negative).
  • Floating point precision — Scores are double-precision floats. Avoid comparing for equality with == in application code; use a tolerance or integer scores if exact equality matters.
  • Exclusive boundaries — Use (inf for infinity, (5 for "greater than 5" (exclusive). Don't leave out the space: (350 is correct, ( 350 is parsed as the member ( 350? No, Redis syntax requires no space between ( and the score: (350.
  • Empty resultsZRANGEBYSCORE returns an empty array if no match. Check your min/max order: ZRANGEBYSCORE key 10 5 returns nothing because min > max.
  • High cardinality — While O(log n) is efficient, if you have millions of members, extremely large range queries (returning millions of results) can be heavy. Use pagination with ZRANGE or ZSCAN for iterating large sets.

What you learned & what's next

You now understand how to add scores and perform range queries in Redis sorted sets. You can: - Use ZADD to insert or update member scores. - Retrieve top members with ZREVRANGE and scores via WITHSCORES. - Filter by score range with ZRANGEBYSCORE. - Count members in a range with ZCOUNT. - Handle ties, exclusive boundaries, and floating-point quirks.

This skill is directly applicable to leaderboards, priority queues, time-series bucketing (store timestamps as scores), and any scenario requiring ordered, score-based data retrieval.

Next step: In the next lesson, you'll learn how to find a member's rank (position) using ZRANK and ZREVRANK, and how to remove members by rank or score range with ZREMRANGEBYRANK and ZREMRANGEBYSCORE.

Practice recap

Now it's your turn: in redis-cli, create a sorted set called leaderboard and add at least 5 members with distinct scores. Practice querying the top 3 using ZREVRANGE, then all members with scores between 300 and 700 using ZRANGEBYSCORE. Finally, update one member's score with ZADD and confirm their new rank. This exercise cements the flow of add-score-query that you'll use in real applications.

Common mistakes

  • Forgetting WITHSCORES when you need both member name and score — without it, you only get the members.
  • Mixing up ZRANGE (ascending by rank) with the more common ZREVRANGE (descending) — top-N queries nearly always want descending.
  • Using ZRANGEBYSCORE with min > max, which returns an empty array — always ensure min ≤ max.
  • Using spaces in exclusive boundaries — correct syntax is (350 (no space), not ( 350.
  • Assuming scores are unique — they are not. Ties are broken by lexicographic member order, which can cause unexpected ranking if you assume strict ordering.

Variations

  1. Use ZREVRANGEBYSCORE instead of ZRANGEBYSCORE when you need descending order by score (highest first).
  2. For paginated leaderboards, combine ZREVRANK (get rank) with ZRANGE by rank range to implement "3rd to 5th place" queries.
  3. If scores are large timestamps (e.g., Unix milliseconds), use ZRANGEBYSCORE to retrieve events within a time window — a form of time-series querying.

Real-world use cases

  • Real-time gaming leaderboard: update player scores with ZADD, show top 10 using ZREVRANGE with WITHSCORES.
  • Priority job queue: assign job priority as score, pop highest priority with ZRANGEBYSCORE ... LIMIT 0 1 then ZREM.
  • Product rating aggregation: store product ratings {member: product_id, score: average_rating}, query top-rated products by score range.

Key takeaways

  • Sorted sets keep members ordered by a numeric score, allowing O(log n) add/update and O(log n + m) range queries.
  • Use ZADD to add or update a member's score — it's an upsert command.
  • ZREVRANGE gives you the highest-to-lowest members by rank, perfect for leaderboards.
  • ZRANGEBYSCORE with exclusive boundaries ((score) enables flexible filtering without manual application logic.
  • Always include WITHSCORES when you need the numeric values alongside member names.
  • Scores can be floats and are not unique — ties are sorted lexicographically by member name.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.