Redis Cheatsheet

Sets

Use this Redis reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

Command table

An unordered collection of unique strings. Membership is O(1), and the set-algebra commands run server side, which is the whole reason to use one: intersecting two follower sets never leaves Redis.

CommandWhat it doesComplexity
SADD key m [m ...]Add members, return how many were newO(n) members
SREM key m [m ...]Remove members, return how many existedO(n) members
SISMEMBER key m1 or 0O(1)
SMISMEMBER key m [m ...]One 1/0 per member, in order (6.2+)O(n) members
SCARD keyCardinality, 0 if absentO(1)
SMEMBERS keyEvery memberO(n) set size
SPOP key [count]Remove and return random membersO(count)
SRANDMEMBER key [count]Return random members without removingO(count)
SMOVE src dst mAtomically move one member between setsO(1)
SSCAN key cursor [MATCH p] [COUNT n]Cursor iterateO(1) per call
SINTER k [k ...]IntersectionO(n×m) worst case
SUNION k [k ...]UnionO(total members)
SDIFF k [k ...]First set minus the restO(total members)
SINTERSTORE dst k [k ...]Intersection into a keyO(n×m)
SUNIONSTORE dst k [k ...]Union into a keyO(total)
SDIFFSTORE dst k [k ...]Difference into a keyO(total)
SINTERCARD n k [k ...] [LIMIT l]Size of the intersection, no result set (7.0+)O(n×m)

SMEMBERS key is exactly SINTER key, and both are O(n) with the entire set on the wire. Use SSCAN above a few thousand members.

Basics

SADD hu:post:42:tags redis cache database
# (integer) 3, and a re-add of "redis" returns 0

SISMEMBER  hu:post:42:tags redis          # 1
SMISMEMBER hu:post:42:tags redis mysql    # 1) 1  2) 0
SCARD      hu:post:42:tags                # 3
SREM       hu:post:42:tags cache          # 1
SMEMBERS   hu:post:42:tags                # redis database, order is arbitrary

Order is not just unspecified, it genuinely varies with the encoding and with insertion history. Never rely on it, and never rely on it being stable between two calls.

Removing the last member deletes the key, so an empty set does not exist and EXISTS returns 0. SCARD on a missing key is 0.

Random members, and the sign rule

SPOP removes, SRANDMEMBER does not. Both take an optional count whose sign changes the semantics.

CallBehavior
SPOP keyOne member, removed
SPOP key 3Up to 3 distinct members, removed
SRANDMEMBER key 3Up to 3 distinct members, capped at the set size
SRANDMEMBER key -3Exactly 3 draws with repetition
SADD hu:raffle:9 a b c > /dev/null
SRANDMEMBER hu:raffle:9 10     # a b c, capped, never repeats
SRANDMEMBER hu:raffle:9 -10    # 10 entries, repeats allowed
SPOP hu:raffle:9 2             # two winners, gone from the set

SPOP with a count is not replicated as SPOP to replicas, it is rewritten as an explicit SREM, which is what keeps a replica consistent with a nondeterministic command. That same nondeterminism is why SPOP inside a Lua script was historically restricted.

Set algebra

The three operations plus their STORE forms are the reason to model relationships as sets.

SADD hu:user:1:follows  10 11 12 > /dev/null
SADD hu:user:2:follows  11 12 13 > /dev/null

SINTER hu:user:1:follows hu:user:2:follows        # 11 12, mutual
SUNION hu:user:1:follows hu:user:2:follows        # 10 11 12 13
SDIFF  hu:user:1:follows hu:user:2:follows        # 10, only user 1 follows
SDIFF  hu:user:2:follows hu:user:1:follows        # 13, order matters

# Persist the answer so you can paginate it with SSCAN
SINTERSTORE hu:tmp:mutual:1:2 hu:user:1:follows hu:user:2:follows
EXPIRE      hu:tmp:mutual:1:2 60

SDIFF is not commutative, it is the first key minus every other key. The STORE variants overwrite dst completely, and an empty result deletes dst rather than leaving an empty set, which quietly breaks code that assumes the destination exists.

SINTERCARD (7.0+) answers "how many" without materializing the intersection, and LIMIT lets it stop early.

SINTERCARD 2 hu:user:1:follows hu:user:2:follows            # 2
SINTERCARD 2 hu:user:1:follows hu:user:2:follows LIMIT 1    # 1, stopped at 1

LIMIT 1 turns "do these sets overlap at all?" into a cheap question on huge sets, since it aborts on the first hit instead of counting every match. LIMIT 0 means no limit.

Redis optimizes SINTER by starting from the smallest set and probing the others, so intersecting a 10-element set against a 10-million-element set is fast. SUNION and SDIFF have no such shortcut and are O(total members) across every key.

Tag and relationship patterns

Tags, both directions. Keep a set per post and a set per tag, and every interesting query becomes set algebra.

SADD hu:post:42:tags   redis cache
SADD hu:tag:redis:posts 42
SADD hu:tag:cache:posts 42 43

SINTER hu:tag:redis:posts hu:tag:cache:posts   # posts tagged BOTH
SUNION hu:tag:redis:posts hu:tag:cache:posts   # posts tagged EITHER
SDIFF  hu:tag:cache:posts hu:tag:redis:posts   # cache but not redis

Both directions must be written together or they drift, so wrap the two SADD calls in MULTI or a Lua script.

Followers and suggestions.

# People user 1 follows who also follow user 1 back
SINTER hu:user:1:follows hu:user:1:followers

# Suggestions: friends of a friend that user 1 does not already follow
SDIFFSTORE hu:tmp:sugg:1 hu:user:2:follows hu:user:1:follows
SREM       hu:tmp:sugg:1 1                    # never suggest yourself

Deduplication. SADD returning 1 means "first time I have seen this", which is a one-command idempotency guard.

if [ "$(redis-cli SADD hu:seen:webhook "$event_id")" = "1" ]; then
  process "$event_id"
fi
redis-cli EXPIRE hu:seen:webhook 86400

That is exact but grows with the number of ids. When an approximate count is enough, switch to a HyperLogLog, which is a fixed 12KB at 0.81 percent error.

Unique daily visitors, exactly.

SADD hu:visitors:2026-09-15 "user:1042"
SCARD hu:visitors:2026-09-15
SINTERSTORE hu:retained hu:visitors:2026-09-15 hu:visitors:2026-09-14

Encoding and sizing

SADD s 1 2 3 ; OBJECT ENCODING s     # "intset", every member parses as an integer
SADD s2 a b  ; OBJECT ENCODING s2    # "listpack", small and non-integer (7.2+)

Three encodings, and conversion is one way:

EncodingUsed whenCost
intsetAll members are integers, count ≤ set-max-intset-entries (512)A sorted int array, tiny
listpackSmall non-integer sets, ≤ set-max-listpack-entries (128) and -value (64)Flat, tiny, O(n) lookup
hashtableAnything largerO(1) lookup, ~50+ bytes per member

An all-integer set is by far the cheapest structure in Redis, so store numeric ids as integers rather than "user:1042" when you have millions of them. Adding one non-integer member converts the whole set permanently.

Set or something else

NeedUse
Unique membership, O(1) checksSet
Unique and ordered by a scoreSorted set
Duplicates allowed, FIFOList
Approximate unique count at scaleHyperLogLog, PFADD and PFCOUNT
Field-value pairs on one entityHash
Top-N by countSorted set with ZINCRBY

The line to remember: a set answers "is X in here" and "what do these two have in common". The moment the question involves ranking, ordering, or a range, it is a sorted set.