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 SISMEMBER for a single membership check; use SMISMEMBER when you need to test several candidates against the same set, to save round trips.
  • Use SCARD to get a set’s size instead of SMEMBERS plus client-side counting — O(1) versus O(N), and no need to transfer the whole set.
  • Remember that SET with EXPIRE-style TTL logic doesn’t apply automatically to sets — if a set like a rate-limit window or session tracker should expire, call EXPIRE on 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 cursor instead, which is cursor-based and non-blocking, the same idea as SCAN for 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 accidental WRONGTYPE collision.

Practice Exercises

  • Create a set named blocked:ips containing three IP-like strings of your choice. Use SISMEMBER to 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 with SREM, and check SCARD again. The final count should be exactly 3.
  • Add four usernames to a set called banned:users, then use a single SMISMEMBER call 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 member tests whether a single member exists in a set in O(1) time, replying 1 or 0.
  • SMISMEMBER key member [member ...] checks multiple members in one round trip, O(N) for N requested members, replying with one 1/0 per member in request order.
  • SCARD key returns 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 SCARD over SMEMBERS-and-count, and SSCAN over full-collection commands, when working with large sets in production.