ZREM and Removing Members
ZREM is the command you use to delete one or more members from a Redis sorted set, without touching the rest of the set. Unlike DEL, which wipes out an entire key, ZREM surgically removes only the members you name — everyone else keeps their score and their position in the ordering. It’s the command behind “remove this player from the leaderboard,” “a user went offline, drop them from the active-users set,” or “this item is no longer for sale, take it out of the price-ranked index.”
Overview / How ZREM Works
Overview / How ZREM Works
A Redis sorted set (zset) is internally backed by two structures working together: a hash table that maps each member to its score for O(1) lookups, and a skip list that keeps members ordered by score so range queries like ZRANGE and ZRANGEBYSCORE are fast. (For small sorted sets, Redis instead uses a compact listpack encoding and upgrades to the skip list automatically once the set grows past the configured thresholds — but the command behavior you see from the client is identical either way.)
When you call ZREM key member, Redis has to do two things: look the member up in the hash table to confirm it exists and find its score, then remove the corresponding node from the skip list using that score. Because Redis is single-threaded, this whole operation happens atomically — no other client can observe the sorted set in a half-removed state, and no other command can interleave in the middle of a ZREM. This is true even when you remove several members in one call: the entire batch either updates the structure member-by-member with no other command running in between, so from every other client’s point of view the removal appears instantaneous.
An important and often-missed detail: sorted sets, like all Redis collection types, cannot exist empty. If ZREM removes the last remaining member of a sorted set, Redis deletes the key entirely. A subsequent EXISTS on that key returns 0, and TYPE reports none, exactly as if the key had never been created. This mirrors how SREM, LREM, and HDEL behave on sets, lists, and hashes — the container is never left around empty.
ZREM only ever operates on members you name explicitly. If you need to remove members based on their score range or their rank (position) rather than by name, Redis gives you two dedicated commands for that: ZREMRANGEBYSCORE and ZREMRANGEBYRANK, covered below. Reaching for ZREM in a loop when one of those fits is a common and avoidable inefficiency.
Syntax
ZREM key member [member ...]
key— the name of the sorted set.member— one or more member names to remove. Member names are matched exactly and are case-sensitive; you can pass as many as you like in a single call.
Return value: an integer reply — the number of members that were actually removed. Members you name that don’t exist in the set are silently ignored and are not counted, so the return value can be less than the number of members you passed in.
| Command | Purpose | Time Complexity |
|---|---|---|
ZREM |
Remove specific members by name | O(M log(N)) for M members removed from a set of N |
ZREMRANGEBYSCORE |
Remove all members whose score falls in a range | O(log(N) + M) for M members removed |
ZREMRANGEBYRANK |
Remove all members within a rank (position) range | O(log(N) + M) for M members removed |
ZSCORE |
Check a member’s score (useful to confirm removal) | O(1) |
ZCARD |
Count members remaining in the set | O(1) |
Examples
Example 1: Removing a single member
ZADD leaderboard:global 100 "alice"
ZADD leaderboard:global 85 "bob"
ZADD leaderboard:global 92 "carol"
ZRANGE leaderboard:global 0 -1 WITHSCORES
ZREM leaderboard:global "bob"
ZRANGE leaderboard:global 0 -1 WITHSCORES
Output:
(integer) 1
(integer) 1
(integer) 1
1) "bob"
2) "85"
3) "carol"
4) "92"
5) "alice"
6) "100"
(integer) 1
1) "carol"
2) "92"
3) "alice"
4) "100"
Three players are added with their scores, and the first ZRANGE shows all three in ascending score order. ZREM leaderboard:global "bob" removes exactly one member and returns (integer) 1 to confirm it. The final ZRANGE shows the set now has only carol and alice, still correctly ordered by score — removing a member never disturbs the relative order of the rest.
Example 2: Removing several members at once, including one that doesn’t exist
ZADD contest:weekly 10 "team_a" 25 "team_b" 5 "team_c" 40 "team_d"
ZREM contest:weekly "team_a" "team_c" "team_z"
ZCARD contest:weekly
Output:
(integer) 4
(integer) 2
(integer) 2
ZADD adds four teams in one call and reports 4 new members. The ZREM call asks Redis to remove three names — team_a, team_c, and team_z — but team_z was never in the set. Redis removes the two that do exist and returns (integer) 2, the count of members actually removed, not the count you requested. ZCARD then confirms two members (team_b and team_d) remain.
Example 3: Removing the last member deletes the key
ZADD online:users 1699999999 "user:501"
EXISTS online:users
ZREM online:users "user:501"
EXISTS online:users
Output:
(integer) 1
(integer) 1
(integer) 1
EXISTS(integer) 0
A single member is added to online:users, and EXISTS confirms the key is present. Once ZREM removes that one and only member, the sorted set becomes empty — and Redis never keeps an empty collection around, so it deletes the key outright. The final EXISTS returns 0: as far as Redis is concerned, online:users no longer exists at all, not even as an empty set.
How It Works Step by Step
When ZREM key member1 member2 ... arrives at the server, Redis processes it as a single atomic unit on the main thread:
- Redis looks up
keyin the main keyspace dictionary. If the key doesn’t exist,ZREMreturns(integer) 0immediately — there’s nothing to remove. - If the key exists but isn’t a sorted set, Redis returns a
WRONGTYPEerror without touching anything. - For each member argument, Redis checks the zset’s internal hash table for that member. If found, it retrieves the member’s current score and deletes the hash table entry.
- Using that score, Redis locates and unlinks the corresponding node from the skip list (or removes the entry from the listpack, for small sets), which is what keeps the remaining members correctly ordered.
- After processing every named member, if the sorted set has zero members left, Redis deletes the key entirely so it never lingers as an empty collection.
- Redis returns the total count of members that were actually found and removed.
Common Mistakes
Mistake 1: Calling ZREM on a key that isn’t a sorted set. Every Redis key has exactly one data type, and type mismatches are a hard error, not a silent no-op.
SET user:1001:status "active"
ZREM user:1001:status "active"
Output:
OK
(error) WRONGTYPE Operation against a key holding the wrong kind of value
Because user:1001:status was created with SET, it holds a plain string, not a sorted set. Calling ZREM on it fails with WRONGTYPE instead of doing anything. Always be sure the key you’re targeting was actually built with ZADD, and use TYPE key to check if you’re unsure.
Mistake 2: Looping ZREM to remove members by score range instead of using ZREMRANGEBYSCORE. Removing expired sessions one at a time with repeated ZREM calls after fetching each member with a separate query wastes round trips and does O(M log N) work per member for something Redis can do in one shot.
ZADD sessions:expiring 1000 "sess:a" 2000 "sess:b" 3000 "sess:c" 9000000000000 "sess:d"
ZREMRANGEBYSCORE sessions:expiring -inf 3000
ZRANGE sessions:expiring 0 -1
Output:
(integer) 4
(integer) 3
1) "sess:d"
A single ZREMRANGEBYSCORE sessions:expiring -inf 3000 call removes every session with a score (timestamp) of 3000 or lower in one atomic step, leaving only sess:d. This is the idiomatic way to expire a batch of scored entries, and it’s both faster and simpler than fetching members and removing them one by one.
Mistake 3: Assuming ZREM errors when the member or key doesn’t exist. It doesn’t — it just returns 0, and code that doesn’t check the return value can silently believe a removal happened when it didn’t.
ZREM leaderboard:missing "ghost"
Output:
(integer) 0
Neither the key leaderboard:missing nor the member ghost exists, but Redis doesn’t raise an error — it reports that zero members were removed. Always inspect the integer return value of ZREM if your application logic depends on knowing whether a removal actually took place.
Best Practices
- Check the integer return value of
ZREMwhen your logic needs to know whether the member actually existed — don’t assume success just because the command didn’t error. - Prefer
ZREMRANGEBYSCOREorZREMRANGEBYRANKover looping individualZREMcalls whenever you’re removing a contiguous range of members, such as expiring old entries or trimming a leaderboard to its top N. - Pass multiple members to a single
ZREMcall rather than issuing one command per member — it’s both faster (one round trip) and still fully atomic. - Remember that removing the last member deletes the key; don’t write code that assumes a sorted set key you created will always still exist later.
- Use
TYPE keyor keep a consistent naming convention (like a:zsetor domain-specific suffix) to avoid accidentally callingZREMon a key created with a different data type. - Combine
ZSCOREwithZREMwhen you need to log or audit what score a member had at the moment it was removed — once removed, that score is gone.
Practice Exercises
- Create a sorted set
race:resultswith five runners and their finishing times as scores. Remove the runner who was disqualified, then confirm the set now has four members usingZCARD. - Build a sorted set
queue:taskswhere the score is a priority number. UseZREMRANGEBYSCOREto remove every task with a priority below a threshold you choose, then verify the remaining tasks withZRANGE queue:tasks 0 -1 WITHSCORES. - Add a single member to a new sorted set, remove it with
ZREM, and then runEXISTSon the key to confirm for yourself that Redis deleted the now-empty set automatically.
Summary
ZREM key member [member ...]removes one or more named members from a sorted set and returns the count actually removed.- Members that don’t exist in the set are ignored, not treated as errors — the return value tells you how many removals really happened.
- If
ZREMempties the sorted set completely, Redis deletes the key itself; it never leaves an empty collection behind. - Calling
ZREMon a key of the wrong type returns aWRONGTYPEerror and changes nothing. - For removing by score or rank range instead of by name, use
ZREMRANGEBYSCOREorZREMRANGEBYRANK— both are more efficient than loopingZREM. - Time complexity is O(M log(N)) for
ZREMremoving M members from a set of N, and O(log(N) + M) for the range-based removal commands.
