Key-Value Stores
In a Nutshell
A key-value store is the simplest database model: you give it a key (a string), and it returns a value (a blob, string, number, or data structure). That's it — no schema, no columns, no joins, no query language beyond "get this key" and "set this key." This simplicity is the point: it enables O(1) lookup and enormous throughput. Key-value stores are the backbone of caching layers, session storage, rate limiters, and counters — anywhere you need fast, simple access by a known identifier.

How It Actually Works
Core Operations
| Operation | Complexity | Example |
|---|---|---|
GET key |
O(1) | GET user:123 → {"name": "Alice", "email": "..."} |
SET key value |
O(1) | SET session:abc {user_id: 123, expires: ...} |
DELETE key |
O(1) | DELETE session:abc |
TTL key seconds |
O(1) | EXPIRE session:abc 3600 (auto-delete after 1 hour) |
INCR key |
O(1) | INCR page_views:home (atomic counter) |
Redis vs DynamoDB vs Memcached
| Aspect | Redis | DynamoDB | Memcached |
|---|---|---|---|
| Storage | In-memory (with optional persistence) | On-disk (SSD) with DAX cache | In-memory only |
| Data structures | Strings, hashes, lists, sets, sorted sets, streams | Items with attributes (more like document-KV hybrid) | Simple strings only |
| Persistence | RDB snapshots + AOF log | Fully durable by default | None — pure cache |
| Scaling | Cluster mode (hash slots) | Automatic partitioning, on-demand scaling | Client-side sharding |
| Best for | Caching, sessions, leaderboards, pub/sub, rate limiting | Serverless apps, any-scale key-value with durability | Pure caching (simple, fast) |
| Latency | Sub-millisecond | Single-digit millisecond | Sub-millisecond |
Use Cases
| Use Case | Key Pattern | Value | Why Key-Value |
|---|---|---|---|
| Session storage | session:{session_id} |
User session JSON | Fast lookup by session ID, TTL for expiry |
| Caching | cache:user:{user_id} |
Cached user profile | Avoid database reads for hot data |
| Rate limiting | rate:{api_key}:{minute} |
Counter | Atomic increment, TTL resets per window |
| Distributed locks | lock:{resource_id} |
Lock holder ID | SET with NX (only if not exists) + TTL |
| Leaderboard | leaderboard:game1 |
Sorted set of scores | Redis sorted sets give O(log n) rank queries |
| Feature flags | flag:{feature_name} |
Boolean/percentage | Fast lookup, instant updates without deployment |
Key Design Patterns
Namespace your keys: Use colons to create a hierarchy: user:123:profile, user:123:sessions, order:456:status. This prevents collisions and makes keys self-documenting.
Use TTL everywhere: Every cached value should have a TTL. Without it, your cache grows unbounded and eventually exhausts memory.
Atomic operations matter: INCR, SETNX (set if not exists), and GETSET are atomic — they prevent race conditions without external locking.

Seeing It in Action
Scenario: Rate limiter using Redis
import redis
import time
r = redis.Redis()
def is_rate_limited(api_key, max_requests=100, window_seconds=60):
"""Sliding window rate limiter using Redis."""
key = f"rate:{api_key}:{int(time.time()) // window_seconds}"
# Atomic increment + set TTL on first use
current = r.incr(key)
if current == 1:
r.expire(key, window_seconds)
return current > max_requests
# Usage
if is_rate_limited("client_abc"):
return HttpResponse(status=429, headers={"Retry-After": "60"})
Why Redis is right here:
- O(1) operations —
INCRis atomic and sub-millisecond - TTL — Keys auto-expire, no cleanup needed
- Throughput — Redis handles 100K+ operations/second on a single instance
- No complex queries — just increment a counter by key
Interview Questions
Q: When would you choose Redis over DynamoDB, and vice versa? Hint: Redis: when you need sub-millisecond latency, rich data structures (sorted sets, pub/sub), and the working set fits in memory. DynamoDB: when you need durable key-value storage, automatic scaling, and serverless pricing. Redis is a cache/accelerator; DynamoDB is a primary database.
Q: How would you implement a distributed lock using Redis? Hint:
SET lock:{resource} {owner_id} NX EX 30— sets the key only if it doesn't exist (NX), with a 30-second TTL (EX). To release: verify you're the owner, then DELETE. Pitfall: if the process holding the lock dies, the TTL ensures the lock is eventually released. For stronger guarantees, use RedLock (consensus across multiple Redis instances).Q: Your Redis instance is running out of memory. What do you do? Hint: 1) Set TTLs on all keys (many apps forget this). 2) Review eviction policy (allkeys-lru is usually best for caches). 3) Check for hot keys consuming disproportionate memory. 4) Scale to Redis Cluster (horizontal sharding by hash slot). 5) Consider tiered caching — Redis for hot data, DynamoDB or disk for warm data.
Q: How does Redis persistence work, and when would you enable it? Hint: Two mechanisms: RDB (periodic snapshots — fast recovery, some data loss) and AOF (append-only log — minimal data loss, slower recovery). Enable persistence when Redis is used as a primary data store (not just a cache). For pure caching, persistence is unnecessary — a miss just means a database read.
Q: Design a leaderboard that shows the top 10 players and any player's rank in O(log n) time. Hint: Redis sorted set:
ZADD leaderboard {score} {player_id}. Top 10:ZREVRANGE leaderboard 0 9 WITHSCORES. Player rank:ZREVRANK leaderboard {player_id}. All O(log n). This is far more efficient than a SQLORDER BY score DESC LIMIT 10on a large table.
References
- Redis Documentation — comprehensive official docs with examples for every command
- DynamoDB Developer Guide — AWS official guide
- Designing Data-Intensive Applications by Martin Kleppmann — Chapter 2 on data models
Dive Deeper
- Redis University — free courses on Redis data structures and patterns
- Redis in Action by Josiah Carlson — practical Redis patterns for real applications
- DynamoDB Single Table Design — Alex DeBrie's guide to advanced DynamoDB modeling