Course · 05

How does Redis stay so fast, and when can it lose your data?

Redis is a data store that lives in memory. This page shows how it works inside, how it saves to disk, how it scales, and the traps interviewers love. Every fact is checked against redis.io and other official docs on 2026-10-25.

TL;DR

Redis is fast because it keeps all data in RAM and runs every command on one main thread, so there are no locks and no disk reads.

  • It is not just a cache: it has lists, hashes, sets, sorted sets, streams, bitmaps, HyperLogLog and geo indexes.
  • Durability is a choice: RDB snapshots can lose minutes; AOF with appendfsync everysec can lose about 1 second.
  • Replication is asynchronous, so Sentinel and Cluster failovers can lose acknowledged writes.
  • Cluster splits keys into 16,384 hash slots; multi-key commands only work when all keys share a slot.
  • Locks with SET NX PX are fine for efficiency; for correctness you also need fencing tokens.
16,384
hash slots in Redis Cluster
1second
writes you may lose with AOF everysec
0.81%
HyperLogLog standard error, using up to 12 KB
512MB
largest single string value

—Words you need

Each jargon word once, in plain words glossary

In-memory
Data lives in RAM, not on disk.
Event loop
One worker that keeps asking "which socket is ready?"
TTL
Time to live: seconds left before a key is deleted.
Eviction
Throwing keys out because memory is full.
RDB
A snapshot file: a photo of all data at one moment.
AOF
Append-only file: a diary of every write command.
fsync
Forcing the OS to really put bytes on disk.
Replica
A read-only copy that follows the primary.
Failover
Promoting a replica when the primary dies.
Hash slot
One of 16,384 buckets that a key falls into.
Pipelining
Sending many commands without waiting for each reply.
Fencing token
A rising number that lets storage reject stale lock holders.

Core ideas

01What Redis is, and why it is fast

RAM, one main thread, an event loop core

Redis means REmote DIctionary Server. It is a key-value store. The value can be a rich data structure, not just a string. All data sits in RAM. Disk is used only to save copies.

One main thread serves every client Three clients send commands to one main thread that runs an epoll event loop and reads and writes data in RAM. Optional I/O threads only help with socket reads and writes. Client AGET Client BINCR Client CZADD Main threadae + epoll Data in RAMno disk read I/O threads6.0+, default off LEGEND Client Runs every command Memory Optional
Many clients, one worker. The worker never waits on disk, so each command is tiny.

Three things make it fast. First, reads never touch disk. Second, one main thread runs every command, so no locks are needed. Third, an event loop with epoll (Linux) lets that one thread watch thousands of sockets. Multiplexing means one thread handles many connections by only working on the ready ones.

In everyday words
One very fast cashier, with all goods on the counter. No walking to the store room, no arguing with other cashiers.
Actually
Redis is "mostly single threaded": one thread serves all requests using multiplexing, while some slow disk work runs in background threads since 2.4 (latency doc). Since 6.0 (GA 30 Apr 2020) Redis can "optionally use threads to handle I/O", which can serve "2 times as much operations per second … when pipelining cannot be used" (6.0 release notes). These I/O threads are off by default and "commands are still executed in the main thread" (redis.conf). The FAQ says Redis is usually memory- or network-bound, not CPU-bound, and can reach 1 million requests per second with pipelining.

Single-threaded means one slow command blocks everyone. That is the price of having no locks.

Source: redis.io latency doc · redis.io FAQ · Redis 6.0 release notes · event library (ae.c)

02Data structures and what each is for

Pick the structure, and the problem often solves itself interview favourite

Redis calls itself a "data structure server". Each type has its own commands, and each command is atomic. Atomic means it runs fully or not at all, with nothing in between.

TypeWhat it isReal useKey commands
StringBytes, up to 512 MBCache a page, counters, flagsSET GET INCR SET NX PX
ListLinked list, fast at both endsJob queue, latest-N feedLPUSH BRPOP LMOVE LTRIM
HashField → value mapUser profile, sessionHSET HGET HINCRBY
SetUnique, unordered members; O(1) add and checkTags, who liked a postSADD SISMEMBER SINTER
Sorted setUnique members ordered by score (skip list + hash table)Leaderboard, time-ordered indexZADD ZINCRBY ZREVRANGE ZREVRANK
BitmapBit operations on a stringDaily active users by user idSETBIT BITCOUNT BITOP
HyperLogLogApproximate count of unique itemsUnique visitors per page per dayPFADD PFCOUNT PFMERGE
GeospatialPoints stored in a sorted set as geohash scoresFind bikes within 5 kmGEOADD GEOSEARCH
StreamAppend-only log with consumer groupsEvent processing, reliable queueXADD XREADGROUP XACK
Leaderboard with a sorted set
ZADD game:scores 4200 "ana" 3100 "raj"     # O(log N) per member
ZINCRBY game:scores 50 "raj"              # add points
ZREVRANGE game:scores 0 9 WITHSCORES      # top 10
ZREVRANK game:scores "raj"                # my rank, O(log N)
In everyday words
A toolbox, not one hammer. A sorted set is a scoreboard that re-sorts itself every time someone scores.
Actually
Sorted sets are "dual-ported" with a skip list and a hash table, so adding is O(log N); equal scores are ordered by member name. HyperLogLog uses up to 12 KB with a 0.81% standard error and counts up to 2^64 items. Bitmaps are "not an actual data type" but bit operations on strings; 512 MB covers 2^32 bits. GEOADD stores points "as a sorted set" using a 52-bit geohash score; there is no GEODEL, you use ZREM. Small hashes, sets and sorted sets use a compact listpack encoding (for example hash-max-listpack-entries 512), using up to 10 times less memory.

In an interview, name the data structure first. "Sorted set" answers leaderboard, ranking and time-window questions.

Source: data types · sorted sets · HyperLogLog · bitmaps · GEOADD · memory optimization

03TTL and how keys really expire

Lazy on access, plus a sampling cleaner 10 times a second deep dive

You set a TTL with EXPIRE key 60 or SET key v EX 60. Writing a new value with SET clears the TTL. INCR, LPUSH and HSET keep it.

WayWhen it runsWhat it does
Lazy (passive)When a client touches the keyIf the key is past its time, delete it and answer "not found"
ActiveEvery 100 ms (10 times a second)Sample 20 keys that have a TTL, delete the expired ones; if more than 25% were expired, repeat
In everyday words
Milk in a fridge. You throw it out when you pick it up and see the date. Someone also checks a few cartons each day so old ones do not pile up.
Actually
The latency doc says the active cycle samples ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP = 20 keys, 10 times per second, and repeats while more than 25% are expired. So if many keys expire in the same second, Redis can block for a while. Expiry error is 0 to 1 ms since 2.6. Replicas do not expire keys themselves: the primary sends a DEL to replicas and to the AOF. TTLs are stored as absolute Unix times, so a big clock jump can expire everything at once.

Add random jitter to TTLs. Many keys expiring in the same second cause latency spikes and cache stampedes.

Source: EXPIRE · latency doc, "Latency generated by expires"

04Eviction when memory is full

maxmemory plus maxmemory-policy core

Set a limit with maxmemory 2gb. When a write would go over it, Redis follows the policy. The default policy is noeviction: writes get an error, reads still work.

PolicyWhich keys can goPick it when
noevictionNone; writes failRedis is a database, losing keys is worse than errors
allkeys-lruAny key, least recently usedPure cache; a good default per the docs
allkeys-lfuAny key, least frequently usedSome keys are always hot, others one-off
allkeys-randomAny key, randomAll keys are used about equally
volatile-lru / -lfu / -randomOnly keys with a TTLCache keys and permanent keys share one instance
volatile-ttlKeys with the shortest TTL leftYou mark throw-away keys with short TTLs
allkeys-lrm / volatile-lrmLeast recently modified (Redis 8.6+)Read-heavy data that is rarely updated
Actually
Redis LRU is approximate: it samples a few keys (maxmemory-samples 5) and evicts the oldest, with a pool of good candidates since 3.0. True LRU would cost more memory. LFU (since 4.0) uses a Morris counter of a few bits that decays every minute and saturates near one million hits. volatile-* policies behave like noeviction if no key has a TTL. Replication and AOF buffers are not counted in maxmemory, so leave some RAM spare.

Cache: allkeys-lru or allkeys-lfu. Primary data: noeviction and alerts on memory.

Source: Key eviction · redis.conf (default noeviction)

Keeping data safe

05Persistence: RDB vs AOF vs both

A photo now and then, or a diary of every write interview favourite

Two ways Redis saves data to disk A write lands in memory first. It is then appended to the AOF file, which is fsynced every second by default, and a forked child process writes periodic RDB snapshots. APPEND FORK Your writeSET k v Memorychanged first AOF filefsync every 1s Childfork() RDB filesnapshot LEGEND Log of every write Snapshot Background process
AOF is a diary of every write. RDB is a photo taken now and then.
OptionHowCan loseGood for
RDBfork() a child that writes a compact snapshot; e.g. save 60 1000Minutes since last snapshotBackups, fast restarts
AOF alwaysfsync on every write batchAlmost nothing; "very very slow"Rare, strict cases
AOF everysec (default)fsync once a second in a background threadAbout 1 secondMost systems
AOF noOS decides (Linux about every 30 s)Up to ~30 sSpeed over safety
RDB + AOFBoth on; AOF is used at restartAbout 1 second"Data safety comparable to PostgreSQL"
NoneNothing on diskEverything on restartPure cache
In everyday words
RDB is a photo of your desk every hour. AOF is writing down every move you make. After a fire, the diary tells you more.
Actually
RDB uses fork() and copy-on-write: parent and child share memory pages, and a page is copied only when it changes. Fork itself blocks the main thread while the page table is copied: a 24 GB instance has a 48 MB page table. The AOF grows forever, so Redis rewrites it in the background (BGREWRITEAOF); since 7.0 it is "multi-part": one base file plus incremental files tracked by a manifest. If both are on, restart loads the AOF "since it is guaranteed to be the most complete".

Fork time per GB of memory, by machine type

Milliseconds per GB, from the redis.io latency doc. Old Xen VMs were 10 to 40 times slower. Hover for exact numbers.

Linode (Xen)
424
EC2 old types (Xen)
239.3
6sync (KVM)
23.3
Physical (unknown)
13.1
VMware VM
12.8
EC2 new types
10
Physical (Xeon)
9
Show the full table
MachineRSS forkedFork time (ms)ms per GB
Linode (Xen)0.9 GB382424
EC2 old types (Xen)6.1 GB1460239.3
6sync (KVM)360 MB8.223.3
Physical (unknown HW)6.1 GB8013.1
VMware VM6.0 GB7712.8
EC2 new types (Xen)1 GB1010
Physical (Xeon 2.27 GHz)6.9 GB629

"Redis is durable" is a config choice, not a fact. Say which fsync policy, and how much loss you accept.

Source: Redis persistence · latency doc, fork table · FAQ (copy-on-write)

06Replication and Sentinel

Async copies, and a voting group that does failover core

Replication and Sentinel failover Clients write to the primary, which copies changes to two replicas without waiting. Three Sentinels watch the primary and, when a majority agree it is down, promote a replica. WRITES ASYNC WATCH PROMOTE Your appclient Primarytakes writes Replica 1read-only Replica 2read-only 3 Sentinelsvote by majority LEGEND Primary Copy Decides failover Watches
Copies are sent without waiting, so a crash can lose the last few writes.

A replica connects and sends PSYNC with the replication ID and the offset it has. An offset is a byte counter of the change stream. If the primary still has those bytes in its backlog (a ring buffer of recent changes), it sends only the gap. If not, it does a full sync: it makes an RDB file, sends it, then streams the buffered writes.

In everyday words
A teacher reads notes out loud and does not wait for students to finish writing. If the teacher faints, the best student takes over, but may have missed the last sentence.
Actually
"Redis uses by default asynchronous replication". WAIT can ask for N acknowledged copies, but it "does not turn a set of Redis instances into a CP system": acknowledged writes can still be lost in a failover. Replicas are read-only by default since 2.6. Sentinel does four jobs: monitoring, notification, automatic failover and acting as a configuration provider (clients ask it for the current primary). The quorum is how many Sentinels must agree the primary is down; but the failover itself needs the vote of a majority of Sentinels. Run at least 3 Sentinels on 3 different machines. Default port is 26379.
Trap from the docs
A primary with persistence off and auto-restart is dangerous: it restarts empty, and the replicas copy the empty data set and wipe themselves.

Sentinel gives high availability, not zero data loss. Say "async replication, small window of lost writes" in every answer.

Source: Redis replication · High availability with Redis Sentinel

07Redis Cluster: slots, redirects, limits

16,384 slots, CRC16, MOVED and ASK interview favourite

How Redis Cluster finds the node for a key A key is hashed with CRC16 and taken modulo 16384 to get a hash slot. Each of three primary nodes owns a share of the slots, and the node that owns the slot serves the key. OWNER Keyuser:42 Hash slotCRC16 % 16384 Node Aslot share 1 Node Bslot share 2 Node Cslot share 3 LEGEND Your key The math Primary node
The key decides the slot. The slot decides the node. No proxy sits in between.

Sharding means splitting data across machines. Redis Cluster does it with hash slots. HASH_SLOT = CRC16(key) mod 16384. Each primary owns some slots. Clients learn the slot map and talk to the right node directly, with no proxy.

ThingWhat it means
Hash tag {...}Only the part inside braces is hashed. {user1000}.following and {user1000}.followers land in the same slot.
-MOVED 3999 127.0.0.1:6381"This slot lives on that node, for good." Update your slot map.
-ASK"This slot is being moved right now. Ask the other node for this one query only."
-TRYAGAINMulti-key command during resharding with keys split across nodes.
Multi-key commandsOnly allowed when all keys hash to the same slot (else a CROSSSLOT error).
Cluster busNode-to-node port = data port + 10000; nodes gossip and vote on failover.
In everyday words
A library with 16,384 shelves split among three rooms. The book title tells you the shelf; the shelf tells you the room.
Actually
The spec uses the XMODEM CRC16 and keeps 14 of its 16 bits, hence modulo 16384. Suggested max size is "~1000 nodes". Write safety is best-effort: "There is always a window of time when it is possible to lose writes during partitions", and "last failover wins". Cluster survives if the majority of primaries is reachable and each unreachable primary has a reachable replica.

Design keys for the cluster from day one: put things you change together under one hash tag, but do not put everything under one tag, or you create a hot shard.

Source: Redis cluster specification

Many commands at once

08Transactions, Lua and pipelining

Three different tools that people mix up deep dive

ToolWhat it guaranteesWhat it does not
MULTI / EXECQueued commands run in one go; no other client runs in the middleNo rollback: if one command fails after EXEC, the others still run. You cannot read a value and decide inside.
WATCHOptimistic lock: if a watched key changes before EXEC, EXEC returns null and you retryRetries can pile up when many clients fight over one key
Lua (EVAL) / Functions (7.0+)Whole script is atomic; can read, decide and write on the serverA slow script blocks the whole server
PipeliningSaves round trips and syscallsNot atomic: other clients' commands can run between yours

10,000 PINGs over loopback, with and without pipelining

Seconds, from the redis.io pipelining page (Ruby client, Mac OS X). Shorter is better.

Without pipelining
1.19s
With pipelining
0.25s
Show the full table
RunSeconds
Without pipelining1.185238
With pipelining0.250783
In everyday words
Pipelining is writing your whole shopping list on one note instead of phoning the shop for each item. A transaction is the shop packing your list without serving anyone else in between.
Actually
Redis "does not support rollbacks" because they would hurt simplicity and performance. Syntax errors found while queuing make EXEC refuse the whole transaction (since 2.6.5); runtime errors like WRONGTYPE do not stop the rest. For scripts, "Redis guarantees the script's atomic execution … all server activities are blocked during its entire runtime". Pipelining helps beyond RTT because many commands share one read() and one write() syscall; throughput can reach about 10 times the baseline. With a 250 ms RTT and no pipelining you get at most 4 requests per second. Send pipelines in batches (the docs suggest about 10k commands) so reply buffers stay small.

Need read-decide-write atomically? Use Lua or a Function. Need speed only? Use pipelining.

Source: Transactions · Scripting with Lua · Pipelining

09Pub/Sub vs Streams

Fire-and-forget vs a stored log with acknowledgements core

Streams with a consumer group A producer appends entries to a stream. A consumer group hands each entry to one of two consumers, and every handed-out entry sits in the pending entries list until the consumer acknowledges it with XACK. UNTIL XACK ProducerXADD Streamappend-only GroupXREADGROUP Worker 1consumer Worker 2consumer PendingPEL LEGEND Stored log Hands out work Not yet confirmed
An entry leaves the pending list only when a worker says "done" with XACK.
QuestionPub/SubStreams
DeliveryAt-most-onceAt-most-once or at-least-once
Subscriber offline?Message is lost foreverMessage waits in the stream
Stored?NoYes, trim with MAXLEN ~ 1000
Work sharingEvery subscriber gets every messageConsumer group splits entries among workers
Crash recoveryNonePending list + XPENDING, XCLAIM, XAUTOCLAIM
Good forLive chat, cache-invalidation pingsJob queues, event processing
In everyday words
Pub/Sub is a radio: if you were not listening, you missed it. A stream is a mailbox with a "read and signed" stamp.
Actually
The Pub/Sub doc says: "If the subscriber is unable to handle the message … the message is forever lost." Stream IDs look like <milliseconds>-<sequence> and stay increasing even if the clock jumps back. A consumer group gives each entry to one consumer; the entry stays in the Pending Entries List (PEL) until XACK. That is at-least-once: a worker that crashes after doing the work but before XACK will see the entry again, so handlers must be idempotent (safe to run twice). Sharded Pub/Sub (SSUBSCRIBE, 7.0+) keeps messages inside one shard instead of flooding the whole cluster bus.

Pub/Sub for "tell whoever is online now". Streams for "this job must be done". Kafka when you need long retention and replay at large scale (see page 09).

Source: Redis Pub/sub · Redis Streams

Using it well

10Common patterns

Seven jobs Redis does every day interview favourite

Cache-aside
Read Redis; on a miss read the DB, then SET k v EX 300
On update: write DB, then delete the key. Use allkeys-lru. Add TTL jitter.
Session store
One hash per session, TTL refreshed on each request
HSET sess:abc user 42 then EXPIRE sess:abc 1800. Needs persistence or replicas if logouts hurt.
Rate limiter
Fixed window: INCR + EXPIRE per user per second
Sliding window: sorted set of timestamps, ZREMRANGEBYSCORE old ones, ZCARD. Wrap in Lua so it is atomic. The INCR doc shows the rate-limiter pattern.
Leaderboard
Sorted set: ZINCRBY, ZREVRANGE, ZREVRANK
All O(log N). Ties are broken by member name.
Counters
INCR / HINCRBY are atomic
Unique counts: HyperLogLog, 12 KB, 0.81% error. Yes/no per user: bitmap.
Queues
Simple: LPUSH + BRPOP. Reliable: BLMOVE or Streams
Sidekiq and Resque use Redis lists. Streams add acks and retry.
Lock
SET res token NX PX 30000
Release only if the value is still yours. Details in section 11.
Sliding-window rate limit (Lua, runs atomically)
-- KEYS[1]=rl:user:42  ARGV: now_ms, window_ms, limit, request_id
redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, ARGV[1] - ARGV[2])
if redis.call('ZCARD', KEYS[1]) < tonumber(ARGV[3]) then
  redis.call('ZADD', KEYS[1], ARGV[1], ARGV[4])
  redis.call('PEXPIRE', KEYS[1], ARGV[2])
  return 1
end
return 0
Actually
Memory sizing from the FAQ (64-bit): an empty instance uses ~3 MB, 1 million small string keys ~85 MB, 1 million hashes with 5 fields ~160 MB. Use these for back-of-envelope sizing, then measure with INFO memory.

Most Redis designs are one of these seven. Say which one, the data structure, and the TTL.

Source: INCR (rate limiter pattern) · Lists · FAQ memory footprint

11Distributed locks: SET NX, Redlock, and the debate

Redis docs and Martin Kleppmann disagree; know both sides hard

SET resource_name my_random_value NX PX 30000   # take lock for 30 s, only if free
-- release only if still mine (Lua, or DELEX ... IFEQ on Redis 8.4+)
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end

The random value stops you deleting someone else's lock after yours expired. Redlock takes the same lock on 5 independent primaries and counts it as held only if a majority (3) said yes within the validity time.

Fencing tokens stop a late lock holder Client 1 gets lock token 33 and then pauses. Its lock expires and Client 2 gets token 34 and writes. When Client 1 wakes up and writes with token 33, storage rejects it because it has already seen 34. TOKEN 33 LATE TOKEN 34 Client 1GC pause Client 2gets lock next Lock servicehands out tokens Storagerejects old LEGEND Client Checks the token Late write
The lock alone cannot stop a paused client. The storage check can.
In everyday words
A meeting-room key that melts after 30 seconds. If you fall asleep holding it, someone else gets a new key. The room should check the key number, not just "has a key".
Actually
Kleppmann (8 Feb 2016): a GC pause or network delay can outlast the lock TTL, so two clients think they hold the lock. The fix is a fencing token, a number that rises with each grant, checked by the storage. Redlock has no way to produce one and assumes bounded delays, pauses and clock drift. His advice: for efficiency, a single Redis is enough; for correctness, use a consensus system such as ZooKeeper with fencing. antirez published a counter-argument. The redis.io page now itself says "You should implement fencing tokens" and warns that Redis TTLs do not use a monotonic clock. This is a contested topic.

Say: "Redis lock for avoiding duplicate work. For money or data correctness, fencing tokens checked by the database, or a consensus store."

Source: Distributed Locks with Redis · Kleppmann, How to do distributed locking · antirez, Is Redlock safe?

12Pitfalls in production

Things that page you at 3 a.m. deep dive

KEYS in app code
KEYS * is O(N) and blocks the one thread
Docs: "Don't use KEYS in your regular application code." Use SCAN with a cursor, or keep your own index set.
SCAN rules
O(1) per call, may return a key twice
Keys present for the whole scan are always returned. Keys added or removed mid-scan may or may not appear. Make the handler safe to repeat.
Big keys
A list or set with millions of items
O(N) commands on it (SMEMBERS, DEL, SUNION) block everyone. Find them with redis-cli --bigkeys / --memkeys (uses SCAN). Delete with UNLINK, which frees memory on a side thread.
Hot keys
One key gets most of the traffic
In a cluster, one node melts while others idle. Find with redis-cli --hotkeys (needs an LFU policy). Common fixes (practitioner advice, not redis.io): local in-process cache, read from replicas, split into key:1..N copies.
Fragmentation
RSS stays high after you delete data
The allocator cannot always give pages back. Ratio = RSS / used memory. Plan RAM for peak usage.
Fork spikes
Latency spike every few minutes
BGSAVE / AOF rewrite forks. Disable transparent huge pages; avoid old Xen VMs; set vm.overcommit_memory=1 so fork does not fail.
Actually
The latency doc names KEYS "a VERY common source of latency". It also says a 1 Gbit/s network adds ~200 µs and a Unix socket ~30 µs, far more than the sub-microsecond command time, which is why round trips matter more than CPU.

Every pitfall here comes from one root: one thread. Anything O(N) or slow blocks everybody.

Source: KEYS · SCAN · redis-cli · memory optimization · latency doc

13Redis vs Memcached, and the licence story

Both are in-memory; only one is a data-structure server from docs

FeatureRedisMemcached
ValuesStrings, lists, hashes, sets, sorted sets, streams, morePlain strings (bytes)
Max value512 MB string1 MB item by default (-I changes it)
ThreadsOne main thread for commands; optional I/O threadsWorker threads, 4 by default
PersistenceRDB, AOFNo RDB/AOF-style durability
Replication / failoverBuilt in: replicas, Sentinel, ClusterNone: "servers are unaware of each other"; the client hashes keys
Pick it whenYou need structures, durability or pub/subSimple, multi-threaded string cache
DateEvent
2024-03-20Redis Ltd: from Redis 7.4, dual licence RSALv2 / SSPLv1; no longer BSD-3. Versions up to 7.2 stay BSD.
2024-03-28Linux Foundation launches Valkey, a fork from Redis 7.2.4 under BSD-3, backed by AWS, Google Cloud, Oracle, Ericsson, Snap.
2025-05-01Redis adds OSI-approved AGPLv3 as a third option, starting with Redis 8.
Actually
RSALv2 forbids offering Redis as a managed service to others; SSPLv1 requires releasing the whole service's source. Neither is OSI open source, which Redis itself admitted. AGPLv3 is OSI-approved. As of 2026-10-25 redis.io docs describe Redis 8.x features (for example LRM eviction in 8.6). Licence terms change; check them before choosing.

In interviews, "Redis or Valkey" is the same design answer. Licensing matters for cloud vendors, not for your design.

Source: docs.memcached.org · memcached configuring (4 threads) · memcached.c (1 MB limit) · Redis licence blog 2024 · Valkey launch · Redis AGPLv3 blog 2025

Interview prep

—When to use what

Situation → pick → why decision table

SituationPickWhy
Cache in front of a DBRedis, allkeys-lru, no or RDB persistenceLosing cache data only costs a refill
Redis holds the only copyAOF everysec + RDB + replicas, noevictionAt most ~1 s of loss, backups, failover
Need HA, data fits one machinePrimary + replicas + 3 SentinelsSimple, automatic failover
Data or writes exceed one machineRedis ClusterShards across 16,384 slots
Read, decide, write atomicallyLua script / FunctionRuns as one step on the server
Many independent commandsPipeliningCuts round trips and syscalls
Notify online usersPub/SubCheap, no storage
Jobs that must not be lostStreams + consumer groupsPEL + XACK = at-least-once
Long retention, replay, huge scaleKafka (page 09)Disk-based log, partitions
Lock to avoid duplicate workSET NX PX on one RedisCheap; rare double-run is acceptable
Lock for correctnessConsensus store + fencing tokensSurvives pauses and clock jumps

—Common mistakes

What weak answers say traps

Trap 1
"Redis is single-threaded, full stop"
Commands are on one thread; I/O threads (6.0+) and background frees exist.
Trap 2
"MULTI rolls back on error"
No. Runtime errors do not stop other commands.
Trap 3
"Replicas make writes safe"
Replication is async; failover can lose acknowledged writes.
Trap 4
"Pipelining is a transaction"
No. Other clients can run in between.
Trap 5
Using Pub/Sub as a job queue
Offline subscribers lose messages forever. Use Streams.
Trap 6
Same TTL for a million keys
Mass expiry blocks and the DB gets a stampede. Add jitter.

—Interview questions

22 questions, easy to design-level, with model answers practice

1. Why is Redis fast if it is single-threaded? easy
Show answer
Because all data is in RAM and each command is tiny, so the CPU is rarely the limit; the docs say Redis is usually memory- or network-bound. One main thread runs commands, so there are no locks or context switches between workers. An event loop (epoll/kqueue) lets that thread serve thousands of sockets, only touching the ready ones. With pipelining one instance can do about 1 million requests per second (FAQ). Since 6.0, optional I/O threads do socket reads and writes, but commands still run on the main thread. Trade-off: one slow O(N) command blocks everyone. Follow-up: "How do you use more cores?" Run more instances or Cluster.
2. RDB vs AOF: which would you enable? easy
Show answer
RDB is a periodic snapshot made by a forked child: compact, great for backups and fast restarts, but you can lose minutes of writes. AOF logs every write; with the default appendfsync everysec you lose at most about 1 second. always is safest but "very very slow". For a cache I would use RDB or nothing. If Redis holds real data I enable both, as redis.io recommends for "safety comparable to PostgreSQL": AOF for small loss, RDB for backups. At restart the AOF is loaded because it is the most complete. Follow-up: AOF rewrite and fork latency.
3. What happens when Redis reaches maxmemory? easy
Show answer
It applies maxmemory-policy. The default, noeviction, rejects writes with an error but keeps serving reads. allkeys-lru or allkeys-lfu evict any key; volatile-* only evict keys with a TTL and act like noeviction if none have one. LRU and LFU are approximate: Redis samples 5 keys by default. For a cache I pick allkeys-lru, or allkeys-lfu if a small set is always hot. I also leave RAM for replication and AOF buffers, which are not counted against maxmemory. Follow-up: how to check the hit rate (keyspace_hits/keyspace_misses in INFO stats).
4. How does key expiry work internally? easy
Show answer
Two ways. Lazy: when a client touches an expired key, Redis deletes it and returns nothing. Active: 10 times a second Redis samples 20 keys that have a TTL, deletes expired ones, and repeats if more than 25% were expired. So memory from never-touched keys is still freed. Risk: if a quarter of TTL keys expire in the same second, the loop can run long and add latency, so I add jitter. Replicas don't expire keys on their own; the primary sends DEL, though replicas hide logically expired keys on reads. Accuracy is 0–1 ms since 2.6.
5. Design a game leaderboard for 10 million players. medium
Show answer
Use one sorted set: member = player id, score = points. ZINCRBY on each score event, ZREVRANGE lb 0 9 WITHSCORES for the top 10, ZREVRANK for "my rank". All are O(log N), so 10 million is fine on one node. Ties are ordered by member name; if I need "first to reach wins", I encode time into the score (e.g. score + small fraction from inverse time). For daily/weekly boards, one key per period with a TTL. For durability, AOF + replica. If writes outgrow one node, shard by region or game, and merge top-K lists for a global view. Follow-up: "around me" view = rank then ZREVRANGE rank-5..rank+5.
6. Build a rate limiter of 100 requests per minute per user. medium
Show answer
Simplest: fixed window. Key rl:{user}:{minute}, INCR, set EXPIRE 60 on the first hit, reject if over 100. Cheap but allows up to 200 across a window edge. Better: sliding window log in a sorted set of request timestamps: remove entries older than 60 s, count with ZCARD, add if under limit. Do it in one Lua script so check-and-add is atomic; separate commands would race. Memory is O(limit) per user. Alternative: token bucket in a hash (tokens, last refill time) updated by Lua. Put the user id in a hash tag so the key stays on one slot. Follow-up: what if Redis is down? Fail open or closed, a business choice.
7. How do you use Redis as a cache without stampedes? medium
Show answer
Cache-aside: read Redis; on miss, read the DB and SET key value EX ttl; on update, write the DB then delete the key. A stampede happens when a hot key expires and thousands of requests hit the DB at once. Fixes: TTL jitter so keys don't expire together; a short lock (SET lock:key 1 NX PX 5000) so only one request rebuilds while others wait or serve stale; refresh-ahead before expiry. Also pick allkeys-lru and watch the hit ratio. Follow-up: why delete instead of update on writes? Avoids racing writers leaving an old value.
8. How does Redis Cluster decide where a key lives? medium
Show answer
slot = CRC16(key) mod 16384 (XMODEM CRC16, 14 of 16 bits). Each primary owns a range of slots. Clients cache the slot-to-node map and go straight to the owner; no proxy. If only part of the key is inside {}, only that part is hashed, so {user42}:cart and {user42}:profile share a slot. Multi-key commands, transactions and Lua only work when all keys are in one slot, else CROSSSLOT. Adding a node means moving slots, not rehashing everything. Suggested max is about 1000 nodes. Follow-up: what about a hash tag shared by everyone? It creates one hot slot.
9. MOVED vs ASK redirect? medium
Show answer
-MOVED slot host:port means the slot now belongs to another node permanently; the client should update its slot map and retry there. -ASK happens only during resharding: the slot is being migrated, this key may already be on the target, so the client sends ASKING plus the command to the target once, without changing its map. During migration, a multi-key command whose keys are split between source and target gets -TRYAGAIN. A complete cluster client must handle both. Why two errors? So clients don't flip their map back and forth mid-migration.
10. Sentinel or Cluster: when do you choose each? medium
Show answer
Sentinel gives high availability for one primary with replicas: monitoring, notification, automatic failover and service discovery. Use it when data and write load fit one machine. Cluster adds sharding: data is split over many primaries, each with replicas, and failover is built in through the cluster bus. Use it when memory or write throughput exceeds one node. Cost of Cluster: multi-key operations need same-slot keys, and clients must be cluster-aware. Both use async replication, so both can lose a small window of writes on failover. Run at least 3 Sentinels on separate boxes; failover needs a majority vote.
11. MULTI/EXEC vs a Lua script. Does Redis roll back? medium
Show answer
No rollback. MULTI queues commands; EXEC runs them back to back with no other client in between. If a command fails at runtime (e.g. WRONGTYPE), the others still run. Syntax errors while queuing make EXEC refuse the whole batch. You can't use a read result inside the transaction; WATCH gives optimistic locking: if a watched key changes, EXEC returns null and you retry. A Lua script (or Function in 7.0+) is atomic too, but can read, branch and write on the server, so it's usually simpler and faster. Risk: a long script blocks the server; keep scripts short and pass every key in KEYS.
12. Pub/Sub vs Streams vs Kafka for events? medium
Show answer
Pub/Sub is at-most-once with no storage: offline subscribers lose messages. Good for live notifications. Streams store entries with IDs, support consumer groups, a pending list and XACK, so you get at-least-once and crash recovery via XAUTOCLAIM; limits are RAM and one node per stream key. Kafka is a disk-based partitioned log with long retention and replay, built for very high volume and many independent consumer groups. My rule: Pub/Sub for "who is online now", Streams for a moderate job queue already on Redis, Kafka for event backbones and analytics. Handlers must be idempotent for at-least-once.
13. Can Redis lose a write it already acknowledged? hard
Show answer
Yes. Replication is asynchronous: the primary replies OK before replicas have the write. If it crashes then, the promoted replica lacks the write. With AOF everysec a crash can also lose up to about a second. WAIT N timeout blocks until N replicas acked, which shrinks the window, but docs say it does not make Redis a CP system. min-replicas-to-write and min-replicas-max-lag reject writes when replicas lag, bounding the loss. Cluster spec: "always a window … to lose writes during partitions". So: don't use Redis as the only store for money; use it with a durable DB.
14. Implement a distributed lock with Redis. Is it safe? hard
Show answer
Acquire with SET res <random> NX PX 30000; release with a Lua check-and-delete (or DELEX … IFEQ in 8.4+) so you never delete someone else's lock. On one instance this fails if the primary dies before replicating. Redlock takes the lock on a majority of 5 independent primaries within the validity time. Kleppmann's critique: a GC pause or network delay longer than the TTL lets two holders act, and Redlock assumes bounded clocks and delays; it has no fencing tokens. Fix: storage rejects writes with a token lower than the last seen. My answer: Redis lock for efficiency (avoid double work); for correctness, a consensus system (ZooKeeper/etcd) with fencing tokens.
15. Someone ran KEYS * in production. What happened and what should they use? hard
Show answer
KEYS is O(N) over the whole keyspace and runs on the single command thread, so every other client waits until it finishes; the docs call it a very common source of latency and say not to use it in app code. Use SCAN cursor MATCH p COUNT n: each call is O(1) and returns a few keys plus a new cursor; repeat until the cursor is 0. Guarantees: keys present throughout are returned; a key may be returned more than once; keys added or removed mid-scan may or may not appear. Better still, keep your own index (a set of ids) so you never need to search. Follow-up: SSCAN, HSCAN, ZSCAN for big collections.
16. One key receives 40% of all traffic. How do you fix the hot key? hard
Show answer
First confirm with redis-cli --hotkeys (needs an LFU policy) or client metrics. In a cluster the slot owner overloads while others idle, and adding nodes doesn't help because one key can't be split by slots. Fixes for reads: a small in-process cache with a short TTL in each app server; read from replicas; or store N copies (key:1..N) and pick one at random. For writes such as a counter: split into N sub-counters and sum on read. Note these are practitioner techniques, not from redis.io. Also check whether the key is big, since hot + big is worst.
17. What is a big key and why is deleting it dangerous? hard
Show answer
A big key is one value with huge size or millions of elements, like a set of all users. O(N) commands on it (SMEMBERS, HGETALL, SUNION) block the main thread; it also makes replication and migration slow and can unbalance cluster memory. DEL frees every element on the main thread, which can stall Redis. Use UNLINK, which removes the key now and frees memory on a background thread. Find big keys with redis-cli --bigkeys or --memkeys, which use SCAN. Prevent them by splitting: e.g. followers:{id}:{bucket}, and iterate with SSCAN.
18. Latency spikes every 5 minutes. What do you check? hard
Show answer
A regular period suggests persistence. RDB save points and AOF rewrites call fork(), which copies the page table on the main thread: about 10–13 ms per GB on modern hardware, but 239–424 ms per GB on old Xen VMs (latency doc). Check latest_fork_usec in INFO. Transparent huge pages make copy-on-write much worse; disable them. Also check mass expiry (many keys with the same TTL), slow commands via SLOWLOG, swapping, and AOF fsync on a busy disk. Fixes: bigger interval, persistence on a replica, disable THP, modern VMs.
19. Design a session store for 10 million logged-in users. design
Show answer
One hash per session: sess:{id} with user id, roles, CSRF token; EXPIRE 30 min, refreshed on each request (sliding expiry). Size: FAQ says 1 million 5-field hashes take ~160 MB, so 10 million ≈ 1.6 GB plus overhead; one node fits, but I'd use a primary + replicas + Sentinel for availability, or Cluster if growth is large. Persistence: AOF everysec, since losing sessions logs everyone out. Policy volatile-lru or noeviction with alerts, since every session has a TTL. Logout = DEL. "Log out everywhere" = keep a set user:{id}:sessions.
20. Count unique visitors per page per day for 1 billion page views. design
Show answer
A set per page per day would store every visitor id: too much memory. Use HyperLogLog: PFADD hll:{page}:{date} visitor_id and PFCOUNT. Each HLL uses at most 12 KB with a 0.81% standard error, no matter how many visitors. Weekly uniques: PFMERGE seven daily keys, which is correct for unions. Set a TTL on old days. If you need exact counts for a few pages, use a set for those only. If you need "did user X visit?" per day with integer ids, use a bitmap instead: 1 bit per user (100 million users ≈ 12 MB per docs). Trade-off: HLL can't list members.
21. What breaks if a primary has persistence off and auto-restarts? hard
Show answer
The replicas get wiped. The primary crashes, a supervisor restarts it fast, and with no RDB/AOF it comes back empty. Replicas reconnect, see a new history, do a full sync and copy the empty data set, deleting their own good copy. With Sentinel, the restart can be quick enough that no failover happens. The replication doc warns about exactly this. Fix: keep persistence on for the primary, or disable automatic restart so Sentinel promotes a replica instead.
22. Redis or Memcached for a page cache? medium
Show answer
For plain HTML/JSON blobs under 1 MB, both work. Memcached is multi-threaded (4 worker threads by default), very simple, and servers never talk to each other; the client hashes keys. Redis adds data structures, persistence, replication, Sentinel/Cluster, Pub/Sub and Lua, and allows values up to 512 MB. I'd pick Redis if I might later need counters, rate limits, sessions or failover in the same system; Memcached if I want the simplest multi-core string cache. Follow-up: cache-aside works the same with either.

—Cheat sheet

Numbers and facts to memorise memorise

FactValueSource
Command threads1 main thread; I/O threads optional (6.0+, off by default)redis.conf, 6.0 notes
Max string value512 MBstrings doc
Max keys per instance2^32 (tested 250 million)FAQ
Default evictionnoevictionredis.conf
LRU sample sizemaxmemory-samples 5eviction doc
Active expiry10×/s, 20 keys, repeat if > 25% expiredlatency doc
AOF default fsynceverysec → lose ≤ ~1 spersistence doc
Default RDB save points3600 s/1, 300 s/100, 60 s/10000 changesredis.conf
ReplicationAsync; PSYNC with replication ID + offsetreplication doc
Sentinel≥ 3 instances; port 26379; quorum detects, majority authorisesSentinel doc
Cluster slots16,384; CRC16 (XMODEM); ~1000 nodes max suggested; bus = port + 10000cluster spec
HyperLogLog≤ 12 KB, 0.81% errorHLL doc
Pipelining batch advice~10k commands per batchpipelining doc
Pub/Sub deliveryAt-most-oncepubsub doc
Memcached4 threads default, 1 MB items, no replicationmemcached docs/source

Reference

—Sources

All checked 2026-10-25 sources

WhatWhere
Eviction policies, LRU/LFU/LRMredis.io/docs/latest/develop/reference/eviction/
RDB, AOF, fsync, multi-part AOFredis.io persistence
Replication, PSYNC, WAITredis.io replication
Sentinel quorum and majorityredis.io sentinel
Cluster slots, MOVED/ASK, write safetyredis.io cluster spec
Transactions, WATCHredis.io transactions
Lua atomicity, Functionsredis.io eval intro
Pipelining numbersredis.io pipelining
Pub/Sub delivery semanticsredis.io pubsub
Streams, PEL, XACKredis.io streams
Expiry, KEYS, SCAN, GEOADD, INCRredis.io commands
Latency, fork table, expiry cycleredis.io latency
Memory, fragmentation, listpackredis.io memory optimization
FAQ: footprint, threads, limitsredis.io FAQ
I/O threads (6.0)Redis 6.0 release notes, redis.conf
Distributed locks, Redlockredis.io distributed locks
Redlock critiqueMartin Kleppmann, 2016-02-08; reply antirez
Licence change, Valkey, AGPLRedis 2024-03-20, Linux Foundation 2024-03-28, Redis 2025-05-01
Memcached factsdocs.memcached.org, configuring, memcached.c

Course

—Keep going

Previous and next pages course