Design a Distributed Cache — LRU, TTL, consistent hashing, hot keys

Design a Distributed Cache

Single-node hash map to LRU + TTL, then distribute: replication for HA, consistent hashing for shards, napkin math for 1TB / 100k RPS, and hot-key read/write mitigations — with interview bars by level.

Understanding the problem

A distributed cache stores data as key-value pairs in memory across a cluster. Unlike a single-node cache bounded by one machine, the cluster partitions and replicates data so you can grow horizontally and survive node failures.

Shared sticky-note boards across desks for partitioned and replicated key-value data.
Partition sticky notes across desks — copy a few so one desk dying does not erase the board.
01CRUD

SET · GET · DELETE.

02TTL

Expire + janitor.

03LRU

Map + DLL.

04Scale

Ring · hot keys.

Pair with Delivery framework, Redis, Consistent hashing, Caching strategies, and Scaling reads.

Functional requirements

Non-functional requirements

Functional and non-functional requirements for a distributed cache.
HA + sub-10ms + 1TB / 100k RPS — durability and strong consistency are out.

Entities and API

Entities are trivial: keys and values (plus expiry metadata once we add TTL). In a real interview, skip or rush this — challenges live elsewhere.

POST, GET, and DELETE endpoints for cache keys.
POST /:key · GET /:key · DELETE /:key — breeze through.
POST   /:key   { "value": "...", "ttl": 60? }
GET    /:key   -> { "value": "..." }
DELETE /:key

High-level design

Build an MVP that satisfies functional requirements on one node. Layer distribution for NFRs in deep dives.

1) SET / GET / DELETE

A cache is a hash table — O(1) lookups and inserts. Host one Cache instance behind an API process.

class Cache:
    data = {}  # hash table

    get(key):
        return self.data[key]

    set(key, value):
        self.data[key] = value

    delete(key):
        delete self.data[key]
Client get/set/delete to Cache containing a Hashmap of Key1-4 to value1-4.
Simple Cache

2) TTL

Store (value, expiry). On get, if expired, delete and return null. On set, expiry = now + ttl when provided.

get(key):
    (value, expiry) = data[key]
    if expiry and currentTime() > expiry:
        delete data[key]
        return null
    return value

set(key, value, ttl):
    expiry = currentTime() + ttl if ttl else null
    data[key] = (value, expiry)

cleanup():  # janitor — expired keys never accessed still waste RAM
    for key, entry in data:
        if entry.expiry and currentTime() > entry.expiry:
            delete data[key]
Cache with Hashmap storing expireTime and a Janitor that runs every n minutes.
Simple Cache with TTLs

3) LRU eviction

Need O(1) lookup and O(1) recency updates. Combine a hash map (key → Node) with a doubly linked list (MRU at head, LRU at tail). On get/set move to front; when over capacity, evict tail.prev.

get(key):
    node = data[key]
    if node.expiry and currentTime() > node.expiry:
        delete data[key]; delete node; return null
    move_to_front(node)
    return node.value

set(key, value, ttl):
    expiry = currentTime() + ttl if ttl else null
    if key in data:
        node = data[key]
        node.value, node.expiry = value, expiry
        move_to_front(node)
    else:
        node = Node(key, value, expiry)
        data[key] = node; add_node(node)
        if size > capacity:
            lru = tail.prev
            delete data[lru.key]; delete lru
LRU Cache with Hashmap to Nodes, doubly linked list, and Janitor for expired nodes.
Simple LRU Cache — access moves to front; tail is next to be evicted.
Takeaway

Single-node spine: hash map + DLL + TTL janitor. All hot-path ops stay O(1). Distribution comes next.

Deep dive: high availability

One node dying takes the whole cache down. Replicate. Open questions: how many copies, which nodes, how to sync, what on partition?

Async replication, sync quorum, and peer-to-peer HA options.
Async (simple/fast) · sync/quorum (safer/slower) · peer-to-peer (scale/complexity).

Deep dive: scalability

1TB and 100k RPS will not fit one box. Shard keys across nodes. (In interviews "partitioning" usually means the same idea as sharding across machines.)

Throughput vs storage napkin math leading to about 50 nodes.
~8 nodes for RPS vs ~50 for RAM — provision for storage.

Deep dive: even key distribution

hash(key) % N works until N changes — then almost every key remaps. Consistent hashing places keys and nodes on a ring; walk clockwise to the first node. Add/remove only remaps the adjacent arc.

Hash ring with n1-n8; hash(key)=28 walks clockwise to n4.
Consistent Hashing — hash(key)=28 → walk clockwise → store on that node.

Deeper ring mechanics: Consistent hashing and Cassandra's partitioner story in Cassandra.

Deep dive: hot reads

Hot keys are often a client traffic property, but interviewers still ask. Viral content pins one shard. Distinguish hot reads vs hot writes.

Client reads hot keys from Hot Key Cache and other keys from Main Cache Nodes.
Dedicated Hot Key Cache — promote viral keys off the main ring.
Client talking to three cache shards each holding copies of hot keys.
Copies of Hot Keys — same hot key on multiple shards; client picks a copy.

See also Scaling reads and celebrity/hot-key notes in Caching strategies.

Deep dive: hot writes

Write batching and key sharding for hot write keys.
Batch counters for 50–100ms · or shard views:vid:1..N and sum on read.

Deep dive: performance

Local O(1) is not enough once every op crosses the network. Reuse what you already designed:

Final design

Client with batch writes and connection pooling routing via consistent hashing to nodes with async read replicas.
Final Design — batching + pooling · ring + hot-key suffixes · async replication to read replicas.

What interviewers expect by level

Mid, senior, and staff expectations for distributed cache design.
Mid: LLD spine. Senior: scale + hot keys. Staff: usually a different question.
Interview takeaway

Mid: correct data structures. Senior: distribute with judgment. Staff: usually pick a harder infra problem.

Wrapping up

Distributed cache interviews reward starting simple (map + LRU + TTL) then earning scale: replication for HA, a ring for shards, napkin math for node count, and honest hot-key tactics. Say durability is out unless they pull it in — caches are allowed to forget.

Related: Delivery framework · Redis · Consistent hashing · Caching strategies · Scaling reads · Rate limiter · Common patterns.

← Lattice