Redis vs. Memcached
When Redis is the right cache and when Memcached still wins: data structure richness, persistence, replication, clustering, memory efficiency, and the operational differences that matter at scale.
TL;DR
| Dimension | Choose Redis | Choose Memcached |
|---|---|---|
| Data structures | Need hashes, sorted sets, lists, streams, or any non-string type | Only caching simple string/blob values |
| Persistence | Need data to survive restarts (sessions, counters, rate limits) | Pure cache where cold start is acceptable |
| HA / Replication | Need automatic failover and read replicas | Node failure = cache miss, and that's fine |
| Memory efficiency | Key count under ~50M, or willing to trade memory for features | 100M+ small string keys where per-key overhead matters |
| Threading | Single-thread ceiling not hit (vast majority of deployments) | Need to saturate 16+ cores with simple GET/SET on one box |
Default answer: Redis. Memcached wins in a narrow band of use cases (pure string caches at massive key counts). For everything else, Redis does what Memcached does plus a lot more.
The Framing
Every few months I see the same Slack thread: "Should we use Redis or Memcached?" The answer is almost always Redis, but the conversation takes forever because people confuse two different questions.
Question one: which is a better cache? Here, Memcached is genuinely competitive. It's simpler, uses less memory per key for string values, and its multi-threaded architecture means a single Memcached node can saturate all available cores for simple GET/SET workloads.
Question two: which is a better data store for things that happen to live in memory? This isn't even close. Redis supports data structures, persistence, replication, pub/sub, Lua scripting, and streams. Memcached supports strings. The moment you need a sorted leaderboard, a rate limiter, session storage with failover, or a pub/sub channel, Memcached is out.
The real decision comes down to this: are you building a pure throwaway cache for string blobs, or do you need any feature beyond GET/SET? If the first, evaluate Memcached. If the second, stop evaluating and use Redis.
How Each Works
Redis: Single-Threaded Event Loop + Rich Data Model
Redis runs a single-threaded event loop for command execution. One thread processes all client commands sequentially, which eliminates locking and makes every operation atomic by default. Starting with Redis 6, I/O threads handle network reads and writes in parallel, but the command execution thread remains single.
The data model is where Redis separates itself. Every value has a type, and Redis provides type-specific operations:
# Strings (same capability as Memcached)
SET session:abc123 "{user_id: 456}" EX 3600
# Hashes: update one field without fetching the whole object
HSET user:456 login_count 0
HINCRBY user:456 login_count 1
# Sorted Sets: leaderboard with automatic ranking
ZADD leaderboard 9842 "user:456"
ZREVRANK leaderboard "user:456" # Returns rank, no app-side sort
# Lists: bounded notification queue
LPUSH notifications:456 "You have a new message"
LTRIM notifications:456 0 99 # Keep last 100
# Streams: append-only log with consumer groups
XADD events * type "purchase" amount "49.99"
Persistence is optional. RDB snapshots create point-in-time dumps; AOF (Append-Only File) logs every write for durability. You can run both. For pure caching, disable persistence entirely and Redis behaves like Memcached with more features.
Replication uses async leader-follower replication. Redis Sentinel monitors the leader and promotes a follower on failure. Redis Cluster shards data across multiple nodes with per-shard replication.
Memcached: Multi-Threaded Slab Allocator + Strings Only
Memcached runs multiple worker threads, each handling connections and executing commands concurrently. On a 16-core machine, Memcached uses all 16 cores for command processing. This is its architectural advantage for raw throughput on simple operations.
The data model is intentionally minimal: keys map to byte arrays. No types, no server-side operations beyond GET, SET, DELETE, and atomic increment/decrement.
# Set a value with 1-hour TTL
set session:abc123 0 3600 42
{serialized_json_bytes}
# Get it back
get session:abc123
# Atomic increment (only works if value is a numeric string)
incr page_views:home 1
Memory management uses a slab allocator. Memory is divided into slabs of fixed sizes (64B, 128B, 256B, up to 1MB). Each value goes into the smallest slab class that fits it. This avoids external fragmentation but wastes space when values don't fill their slab. LRU eviction is per-slab class, not global.
There is no persistence, no replication, and no clustering built in. Clients implement consistent hashing to distribute keys across multiple Memcached nodes. If a node dies, those keys are gone and requests fall through to the database.
Redis Data Structures in Practice
The syntax examples above barely scratch the surface. Here's what these data structures look like in production, where Memcached would require complex, race-prone application code to achieve the same result.
Sorted set leaderboard: A mobile game needs a global ranking across 10 million players. Redis makes the ranking automatic.
# Player finishes a round with a new high score
redis.zadd("leaderboard:season_4", {"player:8829": 15230})
# Get this player's rank (0-indexed from the top)
rank = redis.zrevrank("leaderboard:season_4", "player:8829") # O(log N)
# Top 10 players with scores, one command
top_10 = redis.zrevrange("leaderboard:season_4", 0, 9, withscores=True)
# Players ranked 50-60 (pagination for "players near me")
nearby = redis.zrevrange("leaderboard:season_4", 50, 60, withscores=True)
With Memcached, you'd serialize the leaderboard as JSON, fetch it on every update, sort it in application code, and write it back. Two concurrent updates overwrite each other. Even with CAS tokens, the retry storm at high write rates kills performance.
Hash for user sessions: A session store where you frequently update individual fields without fetching the whole object.
# Create session with multiple fields
redis.hset("session:abc123", mapping={
"user_id": "456", "role": "admin",
"cart_items": "3", "last_active": "1712345678"
})
# Update just last_active (no read-modify-write cycle)
redis.hset("session:abc123", "last_active", str(int(time.time())))
# Increment cart count atomically, no race condition
redis.hincrby("session:abc123", "cart_items", 1)
# Read the full session in one round trip
session = redis.hgetall("session:abc123")
Pub/Sub for cache invalidation fan-out: When a product is updated, every app server needs to purge its local cache instantly.
# Publisher (write path, runs once per product update)
def update_product(product_id, data):
db.update("products", product_id, data)
redis.publish("cache:invalidate", f"product:{product_id}")
# Subscriber (runs on every app server)
def listen_for_invalidations():
pubsub = redis.pubsub()
pubsub.subscribe("cache:invalidate")
for message in pubsub.listen():
local_cache.delete(message["data"])
This fan-out pattern is impossible in Memcached. You'd either poll for changes (adding latency and load) or accept stale data until TTL expiry. I've seen teams build custom invalidation systems on top of Memcached using message queues, which is just reimplementing pub/sub with extra infrastructure.
The Threading Model
This is the single biggest architectural difference, and it matters far less often than people think.
Redis: Single-Threaded Event Loop
Redis processes all commands on a single thread using an event loop (epoll on Linux, kqueue on macOS). Every command runs to completion before the next one starts. This eliminates locking entirely and makes every operation atomic by default.
The single-thread model sounds like a bottleneck, but most Redis commands complete in microseconds. A GET takes ~1ฮผs. A ZADD takes ~5ฮผs. At 5ฮผs per operation, one thread handles 200,000 operations per second. I've rarely seen a production workload where the command thread was the actual bottleneck.
Starting with Redis 6, I/O threads handle network read/write in parallel. The flow: I/O threads receive data from clients, the main thread executes commands sequentially, I/O threads send responses. This addresses the real bottleneck in high-throughput deployments (network serialization), not command execution.
Memcached: Multi-Threaded Worker Pool
Memcached spawns multiple worker threads (typically matching the CPU core count). Each thread handles connections and processes commands concurrently. On a 16-core machine, 16 threads process GET/SET operations in parallel.
For simple string operations, this is genuinely faster per node. When your workload is "saturate a single machine with GET/SET at 2M+ ops/sec," Memcached's threading wins because 16 threads each doing 125K ops/sec outpaces one Redis thread doing 200K ops/sec.
The cost: concurrent data access requires locking. Memcached uses fine-grained locks on its hash table and CAS (Compare-And-Swap) tokens for optimistic concurrency control. Lock contention is minimal for simple key-value operations, which is why the multi-threaded model works well for Memcached's limited command set. It would not work well for complex data structure operations.
The bottom line: Redis's single-thread model is irrelevant for 99% of deployments. If you hit the ceiling, Redis Cluster shards across nodes, each with its own event loop. The only time Memcached's threading is a genuine advantage is when you need maximum raw GET/SET throughput from a single machine and can't (or won't) shard.
Head-to-Head Comparison
| Dimension | Redis | Memcached | Verdict |
|---|---|---|---|
| Data types | Strings, hashes, lists, sets, sorted sets, streams, HyperLogLog, bitmaps, geospatial | Strings only | Redis, decisively |
| Atomic operations | 30+ type-specific commands (HINCRBY, ZADD, LPUSH, XADD) | GET, SET, DELETE, INCR/DECR | Redis |
| Persistence | RDB snapshots + AOF log, configurable | None | Redis |
| Replication | Built-in leader-follower + Sentinel + Cluster | None (client-side consistent hashing) | Redis |
| Threading | Single command thread (I/O threads in 6.x+) | Multi-threaded, saturates all cores | Memcached |
| Memory per key | ~90-100 bytes overhead (type info, LRU, refcount, TTL, encoding) | ~50-60 bytes overhead (slab header, flags, TTL) | Memcached (~40% less) |
| Max value size | 512 MB | 1 MB default (configurable) | Redis |
| Scripting | Lua scripts, atomic server-side execution | None | Redis |
| Pub/Sub | Built-in pub/sub + Streams with consumer groups | None | Redis |
| Cluster management | Redis Cluster with automatic sharding | Client-side consistent hashing | Redis |
I've seen exactly one case where Memcached's threading model was the deciding factor: a workload doing 2M+ simple GET/SET ops per second on a single node. Redis 7's I/O threading closes this gap for network-bound workloads, but the single command-execution thread remains a ceiling for CPU-bound simple operations.
The fundamental tension is feature richness vs. raw simplicity. Redis gives you more at the cost of per-key memory overhead and a single-threaded execution model. Memcached gives you less but does that less with ruthless efficiency.
When Redis Wins
Redis is the right choice for the vast majority of caching and in-memory workloads. Here's when it's clearly better:
You need more than strings. A leaderboard (sorted set), a session store (hash), a rate limiter (INCR with TTL), a message queue (list or stream), a unique visitor count (HyperLogLog). If you're serializing a list into a JSON string just to cache it, you're working around Memcached's limitation.
You need persistence or failover. Session stores, rate limit counters, feature flags. If a node restarts and you lose all sessions, every user gets logged out simultaneously. Redis RDB/AOF means warm restarts. Sentinel means automatic failover. My recommendation: if losing the cached data has any user-visible consequence beyond a brief latency spike, use Redis.
You need atomic server-side logic. Lua scripting on Redis lets you execute multi-step operations atomically without round trips. A sliding-window rate limiter that checks the count, increments, and sets expiry in one atomic script is impossible in Memcached.
You want one system, not two. One technology for caching, sessions, rate limiting, pub/sub, and simple queuing. Running both Redis and Memcached doubles your operational surface for marginal benefit.
When Memcached Wins
Memcached's advantages are real but narrow. Choose Memcached when:
Massive key counts with small string values. At 100 million keys, Redis's per-key overhead adds up. Redis stores ~90-100 bytes of metadata per key (type encoding, LRU clock, refcount, TTL). Memcached stores ~50-60 bytes. At 100M keys, that's a ~4 GB difference in overhead alone. For 500M keys, the gap is 20 GB. I've seen this matter exactly twice in production, both times at companies with 500M+ cached objects.
Pure GET/SET throughput on many cores. If your bottleneck is literally "how many GET/SET ops can one box do" and you're running on a 32-core machine, Memcached's multi-threading wins. Redis 7's I/O threads help with network-bound workloads, but the single command-execution thread remains a ceiling for CPU-bound simple operations.
You want radical simplicity. Memcached has fewer failure modes because it has fewer features. No replication means no replication lag bugs. No persistence means no fsync stalls. No Lua means no runaway scripts. If all you need is a volatile key-value cache and you want the simplest possible operational footprint, Memcached is genuinely simpler.
Existing infrastructure. If your team has been running Memcached for years with operational runbooks, monitoring, and institutional knowledge, migration to Redis has a cost. That cost needs to be justified by a feature you actually need, not by "Redis is better in general."
The Nuance
Here's the honest answer: this is not really a close competition for most teams.
Redis does everything Memcached does, plus persistence, replication, data structures, scripting, pub/sub, and streams. The cost is slightly more memory per key and a single-threaded command execution model that only matters at extreme throughput.
The "Memcached is simpler" argument made more sense in 2012 when Redis Cluster didn't exist and Sentinel was new. Today, managed Redis (AWS ElastiCache, GCP Memorystore, Azure Cache) handles the operational complexity. You get clustering, failover, and monitoring out of the box.
My recommendation: start with Redis. If you later discover that per-key memory overhead is your bottleneck (and you've verified this with actual measurements, not hypotheticals), evaluate Memcached for that specific workload. In practice, I've never seen a team regret choosing Redis, but I've seen several regret choosing Memcached when they later needed sorted sets or pub/sub.
The one genuine hybrid case: some teams run both. Redis for sessions, rate limiting, and pub/sub. Memcached for a massive HTML fragment cache with 200M+ keys. This is valid if the operational overhead of two systems is justified by the memory savings. For most teams, it isn't.
Interview tip: lead with the decision framework
Don't recite features. Say: "For pure string caches at massive scale, Memcached is more memory-efficient. For anything requiring data structures, persistence, or HA, Redis wins. In practice, I'd default to Redis unless I had a specific memory-efficiency reason not to." That shows judgment, not memorization.
Real-World Examples
Twitter (now X): Runs one of the largest Memcached deployments in the world, reportedly over 200 TB of RAM across thousands of Memcached nodes for timeline and object caching. The timeline cache stores serialized tweet objects that are fetched by ID (a perfect Memcached workload: small string values, billions of keys, pure GET/SET). Redis is used separately for ranking signals, rate limiting, and session management. At Twitter's scale (billions of cached objects across the fleet), Memcached's ~40% lower per-key overhead saves hundreds of terabytes of RAM compared to running Redis for the same workload. This is the poster child for Memcached at scale.
Instagram: Moved from Memcached to Redis for their activity feed and feed ranking system. The critical problem: Memcached forced them to serialize ranked feeds as JSON blobs, fetch the entire blob on every update, sort in application code, and write back. Under concurrent load, this created race conditions where activity items were silently dropped. Redis sorted sets (ZADD with timestamp scores) made ranking atomic and eliminated the fetch-sort-store cycle entirely. Instagram reported a 30% reduction in cache-related bugs after the migration. Memcached remains for simpler caches like rendered HTML fragments and user metadata where the value is a simple string blob.
Discord: Runs a hybrid architecture with both Redis and Memcached. Redis powers real-time presence (which users are online), message delivery metadata, and guild (server) settings using hashes and sorted sets. Memcached handles the bulk object cache for user profiles and channel metadata. Discord's engineering team has written about choosing Memcached for specific workloads where the key count is in the hundreds of millions and every value is a simple serialized protobuf blob. The dual-system approach costs operational complexity, but at Discord's scale the memory savings from Memcached on the string-heavy caches justify it.
GitHub: Uses Redis for background job infrastructure (Sidekiq queues processing millions of jobs daily), session storage, and API rate limiting. GitHub's rate limiter uses a Lua script that atomically checks the current count, increments, and sets the TTL in a single round trip, handling millions of rate-limit checks per minute. The atomic increment-with-expiry pattern is something Memcached cannot do without a race condition window between the INCR and the TTL set. GitHub's engineering blog notes that Redis's data structure support eliminated several classes of concurrency bugs in their background job system.
How This Shows Up in Interviews
In system design interviews, this trade-off surfaces when you mention caching. The interviewer may ask "Why Redis over Memcached?" or "When would you use Memcached instead?"
What they're testing: Whether you understand the actual technical differences or just default to "Redis is better." They want to hear you articulate when each tool is appropriate and why.
Depth expected at senior level:
- Know the threading model difference and when it actually matters
- Understand per-key memory overhead and when the gap is significant
- Name specific data structures and their use cases (sorted sets for leaderboards, HyperLogLog for cardinality estimation)
- Know that Redis Cluster handles sharding and Sentinel handles failover
- Explain why single-threaded is not a problem for most workloads (event loop, microsecond operations)
| Interviewer asks | Strong answer |
|---|---|
| "Why did you choose Redis here?" | "We need sorted sets for the leaderboard and pub/sub for real-time notifications. Memcached only supports strings, so it can't handle either without app-level workarounds that introduce race conditions." |
| "Would Memcached be better for this cache layer?" | "For this object cache with 10M keys, no. The memory savings aren't significant at this scale, and we'd lose failover. At 500M+ keys with pure string values, I'd revisit." |
| "How does Redis handle high throughput if it's single-threaded?" | "The event loop processes commands sequentially, but operations are microsecond-fast. A single Redis node handles 100K+ ops/sec. I/O threads in Redis 6+ parallelize network I/O. Beyond that, Redis Cluster shards across nodes." |
| "What happens when a Memcached node fails?" | "Those keys are gone. The client's consistent hashing routes around the dead node, and requests fall through to the database. No replication, no failover. For a throwaway cache, that's fine. For sessions or rate limits, it's not." |
Gotcha: don't say 'Redis is always better'
Interviewers will push back if you dismiss Memcached entirely. Acknowledge the narrow cases where it wins (massive key counts, multi-threaded throughput ceiling) and explain why those don't apply to your design. Blanket answers lose points.
Quick Recap
- Redis supports data structures (hashes, sorted sets, lists, streams) with atomic server-side operations. This eliminates fetch-modify-store race conditions and is the primary reason to choose Redis for most caching workloads.
- Redis supports optional persistence (RDB/AOF), leader-follower replication with Sentinel failover, and Cluster for horizontal sharding. Memcached has none of these. For sessions, counters, or any data where node failure is user-visible, Redis is required.
- Memcached is more memory-efficient for pure string caches: ~50-60 bytes per key versus Redis's ~90-100 bytes. At 100M+ keys, the difference is measured in gigabytes. Below 50M keys, it's noise.
- Memcached is multi-threaded; Redis executes commands on a single thread. At extreme throughput (millions of simple ops/sec on one node), Memcached saturates more cores. Most Redis deployments never hit this ceiling.
- Default to Redis for new systems. Choose Memcached only when you have a proven, measured reason: string-only workload at massive key count, or a demonstrated single-thread bottleneck.
- In interviews, lead with the decision framework ("pure throwaway cache vs. data store with structure"), not a feature comparison list. Show you know when Memcached wins, not just that Redis has more features.
Related Trade-offs
- Caching for cache-aside, write-through, and write-behind patterns
- How Redis works internally for the event loop, data encoding, and persistence mechanics
- Read vs. write optimization for when to optimize the read path vs. the write path
- SQL vs. NoSQL for the broader database selection trade-off that often precedes the cache decision
- Caching vs. freshness for TTL strategies and invalidation patterns