Practice this topic in a realistic system design interview
Not every system needs joins, complex filters, or a relational schema. Sometimes the application already knows exactly what it wants: "give me the session for this session ID" or "give me the cached product for this product ID."
A key-value store is built for that kind of work. It stores a value under a key, then lets you fetch the value later with the same key. Think of it like a very fast dictionary or map.
The tradeoff is simple: key-value stores are fast because they keep the access pattern simple. They usually do not join records, search inside every field, or enforce a detailed schema. Instead, they are good at direct reads, writes, deletes, counters, expirations, and a few small data structures.
The key often tells you the job: session:abc123 for session data, cache:product:555 for a cached product response, or rate:user:1001:minute for a request counter.
You will see this model in caches, session stores, rate limiters, leaderboards, feature flags, and databases that handle very high request volume, such as DynamoDB-style systems.
This chapter explains how key-value stores work, where they shine, and where they become the wrong tool.
Loading simulation...
A key-value store has two parts:
The key is usually a string or a binary value. The value might be a plain string, JSON, a binary object, a counter, a hash, a set, a list, or another type supported by the database.
The basic API is intentionally small:
GET key returns the value for a keySET key value stores or replaces a valueDEL key removes a keyEXISTS key checks whether a key is presentMany stores also support:
EXPIRE key ttl, which removes a key after a time limitINCR key, which safely increments a numeric valueThe important point is that the database can go straight to the key. It does not need to plan a complex query, join tables, or search through many fields.
The exact speed depends on where the data lives, how large the value is, how far away the database is on the network, whether the data is copied to other machines, and how fresh each read must be.
In-memory systems such as Redis or Memcached are common when very fast reads and writes matter. Durable distributed systems such as DynamoDB are usually a little slower, but they can keep data safely over time and scale across many machines.
In a key-value store, the key is the main way to reach data. Good key design makes the system easier to read, debug, scale, and operate.
A consistent naming style makes keys easy to understand and group. Prefixes are especially useful because they show what kind of data a key holds.
Prefixes also help avoid accidental name conflicts. For example, user:1001:profile and session:1001 can safely mean different things.
Keys take memory and storage too. A long key does not matter much in a small system, but it matters when you have tens or hundreds of millions of keys.
Prefer:
Avoid:
A hot key is a key that receives far more traffic than most other keys. One viral post, one popular product, or one global counter can overload the machine that owns that key.
Common fixes include:
These patterns show up again and again in production systems.
Caching is the classic key-value use case. The real data stays in a durable database. The key-value store keeps a faster temporary copy of data that is read often.
Cache-aside flow:
Caching reduces load on the main database and makes repeated reads faster. The downside is freshness. A cached value may be older than the database value until it expires or the application removes it.
Sessions are a natural fit because every request usually carries a session ID.
The access pattern is simple: read the session by ID, update it if needed, and let it disappear automatically after the TTL.
Rate limiters often use counters with expiration.
Each request increments the counter and checks whether the user is still under the limit.
The increment must be atomic, which means the store performs it as one safe operation. If the application reads the number and then writes a new number itself, two requests can race and let too many requests through.
Some key-value stores provide sorted sets or similar structures. Redis sorted sets are a common example.
This works well for questions like "who are the top 10 players today?" or "what is this user's rank?" It is not a good fit for broad analytics across many fields.
Shopping carts, password reset tokens, one-time codes, feature flag snapshots, and short-lived workflow state can all fit well. The pattern is the same: the application knows the key, and the data naturally expires.
Redis and Memcached are common in-memory key-value systems. Both are often used for caching, but they are not the same tool.
Redis stores data mainly in memory and supports several data structures. Strings hold cached values, counters, and tokens. Hashes store small objects with fields.
Sets hold unique members for tags and membership checks. Sorted sets back leaderboards and ranked lists. Lists serve as simple queues or recent-item buffers, and streams are append-only event logs.
Redis also supports expiration, Lua scripts, transactions, replication, clustering, and persistence options.
Redis licensing changed in recent years. Redis moved away from the old BSD license in 2024, and Redis 8 added AGPLv3 as an available license in 2025.
The Linux Foundation also started Valkey, a community fork based on the last BSD-licensed Redis version. For many everyday cache use cases, Redis and Valkey feel very similar because they share the same roots and many of the same commands.
Memcached is simpler. It is mostly a distributed in-memory cache for string or binary values. It does not provide Redis-style data structures, persistence, streams, or rich server-side operations.
That simplicity can be a strength. If you only need a large, fast, disposable cache, Memcached can be easy to understand and operate.
Key-value stores are not all the same. Some are disposable caches. Some are durable primary databases.
In-memory caches such as Redis and Memcached hold fast cached or temporary data. If they lose data, the system should usually be able to rebuild it from somewhere else.
Durable distributed key-value databases such as DynamoDB, ScyllaDB, Aerospike, and Riak can act as primary storage. In that case, the key-value store holds the official copy of the data.
Embedded key-value engines such as RocksDB, LevelDB, and BadgerDB are often used inside other databases or local applications. Coordination systems such as etcd, Consul KV, and ZooKeeper-style services store small but important facts like configuration, service membership, and leader election state.
Redis can write data to disk, but you must choose how much durability you need.
| Mode | How It Works | Trade-off |
|---|---|---|
| Snapshot | Writes a copy of the data every so often | Fast restart, but recent writes can be lost |
| Append-only file | Logs write commands to disk | Safer writes, but more disk and recovery work |
| Both | Uses snapshots and append-only logging | Common practical compromise |
The diagram below shows the basic idea: a write reaches memory first, then Redis may also write it to disk depending on the persistence mode.
Do not assume Redis persistence behaves like a transactional database. It can be durable enough for many workloads, but the settings matter.
Durable key-value databases store data on disk or other long-lived storage and copy it across machines for availability. DynamoDB is a familiar managed example. These systems usually start with a primary key model and may add sort keys, conditional writes, extra lookup paths called secondary indexes, and streams.
They are a better fit when the key-value store is the official place where the data lives.
Key-value stores scale by splitting keys across machines. This is often called partitioning or sharding.
The router maps each key to a shard. This works well for single-key operations because each request has a clear owner.
Multi-key operations are harder. If two keys live on different shards, the system must either coordinate across machines or reject the operation.
Redis Cluster handles this with hash slots. Keys that must be used together can be placed in the same slot with hash tags.
Both keys use the user:1001 tag for routing, so they can live on the same shard.
Distributed key-value stores make different consistency choices. In plain terms, consistency is about how fresh and reliable a read is after a write.
| Model | What It Means | Typical Example |
|---|---|---|
| Strong read | A read returns the latest write the system has accepted | etcd, DynamoDB consistent reads |
| Eventual read | A read may briefly return an older value | replicated caches, DynamoDB default reads |
| Local single-node read | One node sees its own reads and writes in order | standalone Redis |
The right choice depends on the data. An older product page cache may be fine. An older permission decision may be dangerous.
Single-key atomic operations are one of the most useful features of key-value stores. Atomic means the store performs the operation as one indivisible step.
Examples:
Conditional writes help prevent lost updates.
This says: set the key only if it does not already exist, and expire it after 30 seconds.
Distributed locks are easy to use badly. A lock with a TTL is really a lease: "I own this for a limited time." That does not guarantee perfect safety by itself. The process holding the lock may pause, the network may split, or the lease may expire while the work is still running.
For low-risk coordination, a Redis lease can be enough. For high-value correctness, prefer a system designed for coordination, such as etcd, or use fencing tokens. A fencing token is a number that increases every time a lock is granted, so the final resource can reject an old worker that shows up late.
Key-value stores are excellent when access is direct: the application knows the key and wants one value.
| Good Fit | Poor Fit |
|---|---|
| Get session by session ID | Find sessions by browser or city |
| Get product cache by product ID | Search products by text |
| Increment rate counter by user ID | Compute analytics across all users |
| Get cart by user ID | Join carts with orders and payments |
| Store feature flag by key | Search feature flags by any field |
If you need to query by value, join related records, scan large ranges, or calculate totals across many records, use a database built for those patterns.
Some key-value databases add secondary indexes, which let you look up data by fields other than the main key. That can be useful, but it changes the tradeoff. The system is no longer doing only simple key-value lookups.
Choose a key-value store when:
Consider another database when:
Key-value stores trade flexible querying for a small, fast, predictable access pattern. The model is just a key plus a value, and the main path is a direct lookup by key.
Their strengths are fast responses, simplicity, TTLs, counters, and caching. They scale by spreading keys across machines. Their main weakness is any query that does not start with a known key.
The real design question is: can the product access this data by key most of the time? If yes, the model stays beautifully simple. If no, that simplicity becomes a wall you keep running into.
10 quizzes