iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Sorted Sets

A sorted set stores unique string members ordered by a floating-point score. Insert is O(log N); range queries by score or rank are fast. The right primitive for leaderboards, rate limits, time-windowed queues.

ZADD, ZRANGE, leaderboards, time windows

EXAMPLE
# 1) Add + read
ZADD scores 100 ada 88 bo 92 cy
ZSCORE scores ada                       # 100
ZRANK  scores ada                       # 2 (ascending — bo, cy, ada)
ZREVRANK scores ada                     # 0 (descending — ada is top)

# 2) Top-N (descending)
ZREVRANGE scores 0 9 WITHSCORES         # top 10 with scores
ZRANGE   scores 0 9 WITHSCORES          # bottom 10

# 3) Range by score
ZRANGEBYSCORE scores 90 100             # members with 90 <= score <= 100
ZRANGEBYSCORE scores 90 +inf            # at least 90
ZCOUNT        scores 90 100             # how many in that range

# 4) Increment a score
ZINCRBY scores 5 ada                    # ada is now 105

# 5) Remove
ZREM       scores bo
ZREMRANGEBYRANK scores 0 -100           # remove all but top 100
ZREMRANGEBYSCORE scores -inf 0           # remove negative scores

# 6) Cardinality
ZCARD scores                            # total count

# 7) Pop highest / lowest
ZPOPMAX scores                          # 1 highest member + score
ZPOPMIN scores 5                        # 5 lowest
BZPOPMIN scores 30                       # blocking pop with 30s timeout

# === Real recipes ===

# 8) Leaderboard with score + tie-break
# Trick: encode the timestamp in the score (lower is better)
#   composite = score * 1e10 - timestamp_ms
# Higher-score-first; among ties, EARLIEST timestamp wins.
ZADD leaderboard $((1000 * 10**10 - 1717000000)) ada
ZADD leaderboard $(( 950 * 10**10 - 1717000050)) bo
ZREVRANGE leaderboard 0 9 WITHSCORES

# 9) Sliding-window rate limit — 100 requests per minute per user
#   - Score = timestamp
#   - Trim everything older than 60s
#   - Count what remains; reject if >= 100
MULTI
    ZADD rate:user:42 $NOW $NOW       # member must be unique — use a UUID/op id
    ZREMRANGEBYSCORE rate:user:42 -inf $NOW_60
    ZCARD rate:user:42
    EXPIRE rate:user:42 60
EXEC
# Reject if the ZCARD result > 100

# 10) Time-windowed queue — earliest deadline first
ZADD jobs $DUE_TS job:abc
ZADD jobs $DUE_TS job:xyz

# Worker pops the earliest-due job (atomic via Lua or BZPOPMIN)
BZPOPMIN jobs 30

# 11) Top-K with bounded memory — trim after each insert
ZADD top 99 'event-1' 87 'event-2' ...
# Then trim:
ZREMRANGEBYRANK top 0 -1001       # keep top 1000

# 12) Range by lex — alphabetical (when scores are all 0)
ZADD names 0 ada 0 bo 0 cy 0 di 0 ed
ZRANGEBYLEX names "[b" "[d"       # → bo, cy, di
ZRANGEBYLEX names - +              # all

# 13) Node — ioredis
import Redis from 'ioredis';
const redis = new Redis();

await redis.zadd('leaderboard', 100, 'ada', 88, 'bo', 92, 'cy');
const top = await redis.zrange('leaderboard', 0, 9, 'WITHSCORES', 'REV');
// [['ada', '100'], ['cy', '92'], ['bo', '88']]

// Increment after a game
await redis.zincrby('leaderboard', 5, 'ada');

// Rate limit (Lua for atomicity)
const now = Date.now();
const windowStart = now - 60_000;
await redis.multi()
    .zadd(`rate:${userId}`, now, randomUUID())
    .zremrangebyscore(`rate:${userId}`, '-inf', windowStart)
    .zcard(`rate:${userId}`)
    .expire(`rate:${userId}`, 60)
    .exec();

# 14) Performance
#   • ZADD / ZRANGE are O(log N)
#   • Use ZRANGEBYSCORE LIMIT for paginated leaderboards
#   • For huge leaderboards, shard by region and merge top-K with a heap

Why it matters

Sorted sets cover leaderboards, time-windowed rate limits, and earliest-deadline queues with one primitive. The composite-score trick (score * factor - timestamp) gives stable tie-breaks in a single ZADD.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
ZADD leaderboard 1000 "ada" 900 "bo" 1200 "cy"
ZREVRANGE leaderboard 0 9 WITHSCORES
ZINCRBY leaderboard 50 "ada"
ZRANK leaderboard "bo"
Try it Yourself »

Exercise

Add a scored member.

board 1000 "ada"

Discussion

Loading…