10 min read
System Design Index
Start Here
Tier 1 -- Building Blocks
Scale Reads
Scale Writes
Database Selection
Traffic Control
Consistency & Coordination
Estimation
Tier 2 -- Core Systems
Tier 3 -- Location & Real-Time
Tier 4 -- Infrastructure & Data
Tier 5 -- Finance & Commerce
Tier 6 -- Advanced & Collaborative
System Design Index
Start Here
Tier 1 -- Building Blocks
Scale Reads
Scale Writes
Database Selection
Traffic Control
Consistency & Coordination
Estimation
Tier 2 -- Core Systems
Tier 3 -- Location & Real-Time
Tier 4 -- Infrastructure & Data
Tier 5 -- Finance & Commerce
Tier 6 -- Advanced & Collaborative
Key-Value Store (Redis, Memcached — O(1) Lookups)
Tier 1 — Building Block
1. What Is It?
A key-value store is the simplest form of a database: it maps opaque string keys to arbitrary byte-value payloads, with O(1) (constant-time, regardless of how much data is stored) average-time GET and SET operations. There is no schema, no query language, no joins — just PUT(key, value) and GET(key).
The reason they matter: a hash table in RAM is the fastest data structure for point lookups. Redis and Memcached keep their entire dataset in memory, achieving ~100K–1M operations per second at sub-millisecond — orders of magnitude faster than a disk-based relational database. Any system with a read-heavy workload and a hot set of frequently accessed data benefits from a key-value store in front of the primary database.
Beyond simple string caches, Redis extends the model with richer data structures (sorted sets, hashes, streams), enabling use cases like leaderboards, session stores, rate limiters, distributed locks, and message queues — all with the same sub-millisecond SLA.
Your backend serves a social media feed where the same 5% of posts account for 80% of all read traffic. The primary database is a PostgreSQL instance that is becoming a bottleneck under read load. Which architectural choice best addresses this problem, and why?
2. How It Works
Core Data Path
Hash Table Mechanics
Both Redis and Memcached use an open-addressed or chained hash table internally. For each key:
- Compute
hash(key) → bucket index - Follow bucket chain / probe to find or insert the entry
- Return the value or nil
Average case: O(1). Worst case (hash collision storm): O(N) — mitigated by good hash functions (Redis uses SipHash for security; Memcached uses Jenkins hash).
Redis Data Structures
Redis extends beyond simple string → string mapping:
| Data Type | Internal Encoding | Key Operations | O() | Use Cases |
|---|---|---|---|---|
| String | Raw bytes / embstr / int | GET, SET, INCR, DECR | O(1) | Cache any value, counters, sessions |
| Hash | ziplist (small) / hashtable (large) | HGET, HSET, HGETALL | O(1) per field | User objects, configuration maps |
| List | listpack / quicklist | LPUSH, RPOP, LRANGE | O(1) push/pop, O(N) range | Message queues, activity logs |
| Set | listpack / hashtable | SADD, SISMEMBER, SINTER | O(1) member ops, O(N) union/inter | Unique visitors, tags, friend lists |
| Sorted Set (ZSet) | listpack / skiplist+hashtable | ZADD, ZRANK, ZRANGE | O(log N) | Leaderboards, priority queues, rate limiting |
| Bitmap | String (bit manipulation) | SETBIT, GETBIT, BITCOUNT | O(1) per bit, O(N) count | User activity flags, bloom filter |
| HyperLogLog | Probabilistic structure | PFADD, PFCOUNT | O(1) | Cardinality estimation (unique visitors) with ~0.81% error |
| Stream | Radix tree of entries | XADD, XREAD, XACK | O(1) append, O(N) range | Event logs, message broker (lightweight Kafka alternative) |
Redis Persistence Modes
| Mode | Durability | Restart Speed | Write Overhead | Best For |
|---|---|---|---|---|
| No persistence | None — crash = total loss | Instant | None | Pure cache (ephemeral data only) |
| RDB snapshot | Lose up to 5 min of writes | Fast (load binary) | Low (background fork) | Session cache, tolerable data loss |
| AOF (fsync=everysec) | Lose up to 1 sec of writes | Slow (replay all ops) | Medium | Session store requiring durability |
| AOF + RDB hybrid | Lose up to 1 sec of writes | Fast (load RDB, apply short AOF tail) | Medium | Production default recommendation |
Eviction Policies (when memory is full)
| Policy | Behavior |
|---|---|
noeviction | Return error on new writes — never evict. Use when data must never be lost. |
allkeys-lru | Evict least recently used key across ALL keys. Default for pure caches. |
volatile-lru | Evict LRU key only among keys with a TTL set. Protects non-TTL keys. |
allkeys-lfu | Evict least frequently used — better for workloads with long-tail infrequent keys. |
allkeys-random | Random eviction — use when access patterns are truly uniform. |
Your team is building a production leaderboard service that must survive server restarts with minimal data loss, but also needs to restart quickly after a crash. Which Redis persistence mode should you configure?
3. Variants & Comparisons
| Redis | Memcached | |
|---|---|---|
| Data types | Strings, Hashes, Lists, Sets, Sorted Sets, Streams, HyperLogLog, Bitmaps | Strings only |
| Persistence | RDB, AOF, hybrid | None (pure cache) |
| Replication | Leader-follower + Redis Sentinel + Redis Cluster | None (client-side sharding via consistent hashing) |
| Clustering | Redis Cluster (16,384 hash slots, automatic sharding) | Client-side with consistent hashing |
| Throughput | ~100K–200K ops/sec (single thread per data shard; cluster scales linearly) | ~200K–500K ops/sec (multi-threaded, simpler code path) |
| Latency | Sub-millisecond (<1ms P50, ~2ms P99) | Sub-millisecond (<1ms P50, ~1ms P99) |
| Memory efficiency | Slightly higher overhead (due to richer data structures) | Lower overhead (simpler string-only model) |
| Lua scripting | Yes (atomic multi-command scripts) | No |
| Pub/Sub | Yes | No |
| Use when | You need rich data types, persistence, replication, or pub/sub | You need maximum raw throughput for simple string caching and nothing else |
Redis Cluster Architecture:
Your team is building a leaderboard feature for a gaming backend. You need to store player scores, efficiently retrieve the top 100 players in ranked order, persist data across server restarts, and automatically failover if a node goes down. Which choice best fits these requirements?
4. When to Use It (and When NOT To)
Use a Key-Value Store When:
- Session store: HTTP sessions mapped by
session_id → user_dataJSON. Fast O(1) lookup; -based expiry for automatic cleanup. - Cache:
product:123 → serialized product JSON. pattern in front of a PostgreSQL or MySQL DB. - Distributed :
rate:user:123 → INCR counter with. Redis INCR is atomic — safe for concurrent rate limiting across multiple app servers. - : Redis SETNX (set if not exists) + TTL implements a basic distributed mutex. More robust: Redlock algorithm across multiple Redis nodes.
- Leaderboard:
ZADD leaderboard <score> <user_id>with sorted set.ZRANGE leaderboard 0 9 REV WITHSCORESfor top 10 in O(log N + K). - / lightweight : Redis Streams or PubSub for event broadcasting within a service mesh (not as durable as ).
Decision triggers:
- "If you have a hot read path hitting the DB > 1000 QPS with the same keys → add Redis "
- "If you need distributed rate limiting across multiple app servers → Redis INCR + TTL"
- "If you need a real-time leaderboard → Redis Sorted Set"
Do NOT Use a Key-Value Store When:
- Complex queries needed: Any JOIN, aggregate, , or full-text search. Key-value stores have no query language — use a relational DB or search engine.
- Strong ACID transactions across multiple keys: Redis has MULTI/EXEC (optimistic transactions), but it's not equivalent to database ACID. For financial records, use PostgreSQL.
- Primary durable store for critical data without a backup DB: Redis persistence (AOF) can still lose data. Use a relational DB as the source of truth; Redis as a cache/secondary store.
- Large values: Storing 10MB blobs per key wastes memory fast. Redis is cost-effective for small-to-medium values (< 10KB per key is typical).
Anti-patterns:
- Storing too much in one key: A hash key with 10M fields degrades to O(N) for HGETALL. Design keys to be small and focused.
- No TTL on cache keys: Memory fills up permanently. Always set a TTL on cached data.
- Redlock for linearizability: Redlock provides distributed mutex semantics but NOT linearizability guarantees. For true distributed coordination, use ZooKeeper or etcd (CP systems).
- Using Redis as primary DB without understanding persistence: Default Redis config has no persistence. A restart = all data gone. Explicitly configure AOF or RDB for production.
Your fintech startup processes payments and needs to store transaction records that must survive server restarts, support balance calculations across multiple accounts, and guarantee that a debit and credit always happen together or not at all. A teammate suggests using Redis as the primary database since it's fast. What is the strongest reason to reject this approach?
5. Real-World Usage
Twitter (Redis for timelines): Twitter stores pre-computed home timelines as Redis lists. For active users, the most recent ~800 tweet IDs are stored in memory. LPUSH timeline:user_id tweet_id on write (fan-out); LRANGE timeline:user_id 0 19 for the first page of 20 tweets. Sub-millisecond for timeline reads, serving ~300K timeline reads/sec at peak.
Stack Overflow (Redis for everything): Stack Overflow uses Redis for session storage, rate limiting, distributed locking, real-time view counts, and question score counts. Their architecture paper notes they serve the entire site from a few high-memory Redis instances with ~99.9% of requests hitting Redis before the DB. Their Redis hit rate exceeds 99% for most endpoints.
Discord (Redis for presence): Discord stores online/offline presence for 500M+ users using Redis. User presence is a Redis hash (HSET presence:user_id status online device mobile). Redis -based expiry handles automatic offline detection when heartbeats stop. Redis broadcasts presence changes to subscribed guild channels. At scale, Discord shards presence Redis across hundreds of nodes.
A social media platform stores each user's home feed as a Redis list of post IDs. When a user goes offline without explicitly logging out, their presence status needs to automatically switch to 'offline' after a period of inactivity. Which Redis feature is best suited to handle this automatic status transition?
6. Interview Cheat Sheet
Key sentences to demonstrate depth:
- "Redis gives you O(1) average GET/SET at sub-millisecond by keeping the entire dataset in memory — 100K–200K ops/sec per shard, scaling linearly with Redis Cluster."
- "Beyond simple caching, Redis's rich data types unlock use cases a plain key-value cache can't do: sorted sets for leaderboards, atomic INCR for rate limiting, and SETNX for distributed locks."
- "Redis vs. Memcached: Memcached wins on raw for simple string caching (multi-threaded); Redis wins on everything else — persistence, , rich data types. Default to Redis unless you have benchmarked a bottleneck Memcached solves."
- "For production, I'd use AOF + RDB hybrid persistence with Redis Sentinel for HA — async with automatic failover in ~30 seconds if the primary goes down."
- "Memory is the constraint. At ~720/year — much cheaper than DB compute for the same QPS. But I'd design to keep individual values small (<10KB) and always set TTLs."
Common follow-up questions:
| Question | Concise Answer |
|---|---|
| "What is the Redis eviction policy and which do you use?" | For pure caches: allkeys-lru (evict least recently used among all keys). For a mix of persistent + cached data: volatile-lru (only evict keys with TTL set). Never use noeviction for a cache. |
| "How do you handle Redis going down?" | Redis Sentinel: monitors primary, promotes replica in ~30s. Redis Cluster: automatic failover built in. Application: must tolerate cache misses and fall back to DB (size DB to handle ~5x normal read load). |
| "How do you implement a distributed rate limiter in Redis?" | MULTI / INCR rate:{user_id} / EXPIRE rate:{user_id} 60 / EXEC — atomic increment + TTL. More robust: Redis Lua script that checks and increments atomically. Alternatively, sliding window with sorted set. |
| "What is the hot key problem in Redis?" | One key (e.g., celebrity_profile:bieber) receiving millions of requests/sec, overloading one Redis shard. Fix: replicate the hot key to N local cache copies, route requests round-robin. |
| "How does Redis Cluster handle multi-key operations?" | Multi-key ops (MGET, pipelines) only work if all keys hash to the same slot. Use hash tags {user:123}:field1 to force multiple keys to the same slot. |
Connections to other building blocks:
- Caching Strategies: Redis is the most common implementation of , write-through, and write-back caching patterns.
- : Redis Cluster uses a variant of with 16,384 fixed hash slots. Memcached uses client-side consistent hashing via twemproxy or ketama.
- : Redis is the standard backend for distributed rate limiters due to atomic INCR and operations.
- Distributed Locking: Redis SETNX + is the basic . Redlock uses multiple Redis nodes for higher .
- Message Queues: Redis Streams provide lightweight and consumer group semantics — simpler than for low-volume event broadcasting.
Glossary History
Click dotted jargon to save explanations here.
Glossary History
Click dotted jargon to save explanations here.