Why SCAN Exists
The KEYS command returns every key matching a pattern in a single call, but it does so by walking the entire keyspace synchronously in one atomic operation, which means Redis cannot serve any other command while KEYS runs. On a database with millions of keys this can block the single-threaded Redis event loop for seconds, stalling every client connected to that instance. SCAN was introduced specifically to solve this: it is a cursor-based iterator that returns a small batch of keys per call along with a cursor to resume from, letting you walk the entire keyspace incrementally while Redis continues serving other commands between calls.
Cricket analogy: KEYS is like demanding the entire ball-by-ball commentary of every match played in IPL history read aloud in one uninterrupted broadcast before any live match coverage can resume — SCAN is like getting one over's commentary at a time, so the broadcast can cut back to live cricket between overs.
How the Cursor Protocol Works
A SCAN call takes a cursor (starting at 0) and returns a pair: a new cursor and a batch of keys. You keep calling SCAN with the returned cursor until Redis returns a cursor of 0 again, which signals the iteration is complete. Crucially, SCAN does not guarantee a fixed number of keys per call (the COUNT option is a hint, not a strict limit), does not guarantee no duplicates across calls (your application code should be idempotent to seeing the same key twice), but does guarantee that every key present for the entire duration of a full scan will be returned at least once, even as the dataset is concurrently modified.
Cricket analogy: The cursor is like a bookmark a scorer uses when reviewing overs from a rain-delayed match in installments — each time they resume, they pick up exactly where the bookmark left off, until they reach the final ball and the review is complete.
import redis
r = redis.Redis(host="localhost", port=6379)
cursor = 0
matched_keys = []
while True:
cursor, keys = r.scan(cursor=cursor, match="session:*", count=200)
matched_keys.extend(keys)
if cursor == 0:
break
print(f"Found {len(matched_keys)} session keys")
# redis-py also provides a generator that hides the cursor loop:
for key in r.scan_iter(match="cache:product:*", count=200):
r.delete(key)SCAN Variants for Collections
The same cursor-based approach is available for iterating inside large individual data structures: HSCAN walks the field-value pairs of a hash, SSCAN walks the members of a set, and ZSCAN walks the member-score pairs of a sorted set, all with the same MATCH and COUNT options as SCAN. These matter because a single Redis key can itself hold millions of elements (a hash with a million fields, for example), and commands like HGETALL or SMEMBERS that return everything at once carry the exact same blocking risk on a single giant key that KEYS carries across the whole keyspace.
Cricket analogy: HSCAN on a giant hash is like paging through a full IPL player-stats spreadsheet 50 rows at a time rather than demanding Excel render all 500 players' career stats in one frozen screen refresh, same risk as KEYS but scoped to one giant file.
SCAN's 'at least once' guarantee is weaker than it might first appear: if a key is added and removed multiple times during a long-running scan, it might be returned, missed, or returned more than once depending on exact timing. Never rely on SCAN for operations requiring an atomically consistent snapshot of the keyspace — use it for maintenance tasks like bulk expiry audits, cache invalidation sweeps, or monitoring, not for correctness-critical reads.
Practical Usage Patterns
A common production pattern is combining SCAN with MATCH to find keys under a specific namespace (as covered in key naming conventions) and then batching the results into pipelined DEL or TTL-check calls, rather than issuing one round trip per key. Tools like redis-cli --scan --pattern 'cache:*' | xargs redis-cli DEL provide a quick command-line equivalent, and most client libraries (redis-py's scan_iter, node-redis's scanIterator, Jedis's ScanParams) wrap the cursor loop into a simple iterator so application code rarely needs to manage the cursor manually.
Cricket analogy: Batching SCAN results into pipelined deletes is like a groundskeeper collecting all the used practice balls from one section of the nets in a single sweep with a ball-collector cart, rather than walking back to the shed after picking up each individual ball.
In Redis Cluster mode, SCAN operates per-node, not across the whole cluster in one logical cursor — client libraries that support cluster mode (like redis-py's RedisCluster or Jedis's cluster client) handle iterating every shard and stitching results together for you, but if you implement SCAN manually against a cluster you must loop it once per node.
- KEYS blocks the entire server while scanning the full keyspace synchronously; SCAN iterates incrementally without blocking.
- SCAN returns a cursor and a batch of keys; you repeat calls until the cursor returns to 0.
- COUNT is a hint, not a strict limit, and SCAN may return duplicate keys across calls.
- SCAN guarantees every key present for the full duration of the scan is returned at least once, even with concurrent modifications.
- HSCAN, SSCAN, and ZSCAN apply the same cursor pattern inside a single hash, set, or sorted set.
- SCAN is not suitable for operations requiring an atomically consistent snapshot; use it for maintenance, not correctness-critical reads.
- In Redis Cluster mode, SCAN operates per-node; cluster-aware client libraries stitch multi-node iteration together automatically.
Practice what you learned
1. Why can KEYS be dangerous to run on a production Redis instance with millions of keys?
2. What does a SCAN call return?
3. What does the COUNT option in a SCAN call actually control?
4. Which command would you use to iterate the fields of a very large single hash without blocking?
5. Is SCAN suitable for operations that require an atomically consistent snapshot of the keyspace?
Was this page helpful?
You May Also Like
Redis Key Naming Conventions
Understand why disciplined key naming matters in Redis and how to design namespaces that scale across teams and services.
Key Expiration and TTL
Learn how Redis lets keys expire automatically using TTLs, and how expiration is implemented internally through lazy and active mechanisms.
Redis Transactions: MULTI/EXEC
Understand how Redis groups commands into atomic transactions using MULTI, EXEC, DISCARD, and optimistic locking with WATCH.