SISMEMBER and Set Cardinality
A Redis Set is an unordered collection of unique strings, and two of its most useful commands are SISMEMBER (plus its multi-member sibling SMISMEMBER), which answer "is this value in the set?", and SCARD, which answers "how many members does this set have?". Both are O(1) operations, which is exactly why sets are the go-to structure for tracking membership — tags on an article, roles a user holds, IP addresses that hit a rate limiter — without ever scanning the whole collection. This lesson covers how sets are stored internally, the exact syntax and complexity of each command, several worked examples, and the mistakes people make when they reach for the wrong tool.
Overview: How Sets Store Their Members
A Redis set (created with SADD) holds an unordered collection of unique strings — adding the same member twice is a no-op, and there is no concept of position or index like a list has. Internally, Redis picks one of three encodings automatically based on the set’s size and contents, and this choice is invisible to your commands but matters for memory and performance: intset, used when every member is an integer and the set is small (up to set-max-intset-entries, default 512) — members are stored as a compact sorted array of integers; listpack, used for small sets that are not pure integers (up to set-max-listpack-entries, default 128, and each value under set-max-listpack-value bytes, default 64) — a compact serialized list scanned linearly; and hashtable, used once a set outgrows those limits — a real hash table where each member is hashed to a bucket for near-constant-time lookup. Redis upgrades a set from intset/listpack to hashtable automatically as it grows; it never downgrades back.
SISMEMBER key member tests whether a single value belongs to the set: for a hashtable-encoded set this hashes the member and checks the bucket; for intset/listpack it searches the (small) array. Either way, because these small-set encodings are capped in size, the practical cost stays effectively constant, which is why Redis documents SISMEMBER as O(1). SCARD key is even simpler — Redis stores the member count as a field on the set object itself, updated on every SADD/SREM, so reading it never touches the members at all. This is the same trick Redis uses for LLEN, HLEN, and ZCARD: the count is maintained incrementally, not computed on demand.
Syntax
SISMEMBER key member
SMISMEMBER key member [member ...]
SCARD key
| Command | Arguments | Returns | Time Complexity |
|---|---|---|---|
SISMEMBER key member |
key — the set’s key name.member — the value to test. |
1 if the member is in the set, 0 if it isn’t (or the key doesn’t exist). |
O(1) |
SMISMEMBER key member [member ...] |
key — the set’s key name.one or more member values to test. |
an array of 1/0 integers, one per member, in the order you asked. |
O(N), N = number of members requested |
SCARD key |
key — the set’s key name. |
the integer count of members, or 0 if the key doesn’t exist. |
O(1) |
For reference, the commands used to build the example sets below are SADD key member [member ...], O(1) per member added, and SREM key member [member ...], O(N) for the number of members removed.
Examples
Example 1: basic membership and count. Tag an article with topics, then check individual tags and the total tag count.
SADD tags:article:42 redis database nosql
SISMEMBER tags:article:42 redis
SISMEMBER tags:article:42 mongodb
SCARD tags:article:42
Output:
(integer) 3
(integer) 1
(integer) 0
(integer) 3
SADD reports 3 because all three tags were new. SISMEMBER tags:article:42 redis returns 1 since "redis" was added, while SISMEMBER tags:article:42 mongodb returns 0 because that tag was never added — note this is a normal, successful reply, not an error. SCARD confirms the set holds exactly 3 members.
Example 2: checking several members at once with SMISMEMBER. Instead of issuing three separate SISMEMBER calls (three round trips), check three roles in a single command.
SADD user:1001:roles admin editor
SMISMEMBER user:1001:roles admin viewer editor
Output:
(integer) 2
1) (integer) 1
2) (integer) 0
3) (integer) 1
Two roles were added, so SADD returns 2. SMISMEMBER then returns one reply per requested member, in the same order they were listed: admin is present (1), viewer is not (0), and editor is present (1). This is the right tool whenever you need to test a batch of candidates against one set without paying for N separate network round trips.
Example 3: a realistic online-users tracker. A common pattern is maintaining a set of currently-connected user IDs, checking a specific user, and updating the count as people disconnect.
SADD online:users alice bob carol
SCARD online:users
SISMEMBER online:users bob
SREM online:users bob
SCARD online:users
SISMEMBER online:users bob
Output:
(integer) 3
(integer) 3
(integer) 1
(integer) 1
(integer) 2
(integer) 0
Three users are added and SCARD confirms 3. SISMEMBER online:users bob confirms bob is online (1). After SREM online:users bob removes him (returning 1, the number of members actually removed), SCARD drops to 2 and a repeat SISMEMBER check now correctly returns 0. Notice SCARD never had to re-scan anything — it simply reflects the maintained counter after each mutation.
How It Works Step by Step
When you run SISMEMBER key member: (1) Redis looks up key in the main keyspace hash table to locate the set object and confirms its type is actually a set — if it’s any other type, this step raises WRONGTYPE immediately. (2) Depending on the set’s current encoding, Redis either hashes member and probes the corresponding hashtable bucket, binary-searches the sorted intset array, or linearly scans the small listpack. (3) It returns 1 if found, 0 otherwise — including when the key doesn’t exist at all, which is treated the same as "not a member," not an error. Because Redis is single-threaded, this whole lookup runs to completion with no other command interleaving, so the answer is always consistent with the exact state at that instant. SCARD skips steps 2 and 3 entirely: after confirming the key’s type, it just reads the object’s stored length field and returns it — no traversal of the set’s members happens at all, which is why it stays O(1) no matter how large the set grows.
Common Mistakes
Mistake 1: calling a set command on a key that isn’t a set. Every Redis key has exactly one type, fixed at creation. Running SISMEMBER or SCARD against a key created with SET, LPUSH, or HSET always fails:
SET user:1001:name "Ada"
SISMEMBER user:1001:name "Ada"
Output:
OK
(error) WRONGTYPE Operation against a key holding the wrong kind of value
The fix is simply to use a distinct key for the set (for example user:1001:tags) rather than reusing a key that already holds a string, hash, or list.
Mistake 2: fetching the whole set just to count it. It’s tempting to call SMEMBERS and count the returned array in application code, but that’s an O(N) operation that transfers every member over the network — wasteful when all you need is the size:
SADD active:sessions:20260810 sess1 sess2 sess3 sess4 sess5
SMEMBERS active:sessions:20260810
SCARD active:sessions:20260810
Output:
(integer) 5
1) "sess1"
2) "sess2"
3) "sess3"
4) "sess4"
5) "sess5"
(integer) 5
Both approaches report 5 members here, but on a set with a million entries the SMEMBERS approach ships a million strings across the wire and blocks on serialization, while SCARD answers instantly regardless of size. Always reach for SCARD when you only need the count.
A third, subtler trap: don’t assume SISMEMBER‘s reply is a boolean at the protocol level — redis-cli and the RESP protocol return it as an integer reply (1 or 0), not the literal words "true"/"false". Client libraries often convert it to a native boolean for convenience, but if you’re scripting against raw redis-cli output or writing a Lua script, remember you’re comparing integers.
Best Practices
- Use
SISMEMBERfor a single membership check; useSMISMEMBERwhen you need to test several candidates against the same set, to save round trips. - Use
SCARDto get a set’s size instead ofSMEMBERSplus client-side counting — O(1) versus O(N), and no need to transfer the whole set. - Remember that
SETwithEXPIRE-style TTL logic doesn’t apply automatically to sets — if a set like a rate-limit window or session tracker should expire, callEXPIREon it explicitly after creating it, or it will live forever. - For large production sets, never iterate with a single blocking command; page through with
SSCAN key cursorinstead, which is cursor-based and non-blocking, the same idea asSCANfor the whole keyspace. - Choose a plain set over a sorted set when you only need uniqueness/membership with no ordering or ranking — sorted sets carry extra memory overhead for the score that a plain set doesn’t need.
- Design key names with a clear namespace (
tags:article:42,online:users) so it’s obvious from the key alone what type and purpose it serves, reducing the risk of an accidentalWRONGTYPEcollision.
Practice Exercises
- Create a set named
blocked:ipscontaining three IP-like strings of your choice. UseSISMEMBERto check one address you added and one you didn’t — predict the two integer replies before you run them. - Build a set of five tag names for a blog post, confirm the count with
SCARD, remove two tags withSREM, and checkSCARDagain. The final count should be exactly 3. - Add four usernames to a set called
banned:users, then use a singleSMISMEMBERcall to check four different usernames (some overlapping, some not) against it. Match each position in the reply array back to the username you asked about.
Summary
SISMEMBER key membertests whether a single member exists in a set in O(1) time, replying1or0.SMISMEMBER key member [member ...]checks multiple members in one round trip, O(N) for N requested members, replying with one1/0per member in request order.SCARD keyreturns a set’s cardinality in O(1) because Redis maintains the count directly rather than counting members on demand.- Sets are encoded internally as intset, listpack, or hashtable depending on size and content — this affects memory and is invisible to the commands you run.
- Calling any set command on a key holding another type raises
WRONGTYPE Operation against a key holding the wrong kind of value. - Prefer
SCARDoverSMEMBERS-and-count, andSSCANover full-collection commands, when working with large sets in production.
