Before you start
LRU Cache is the design problem that tests whether you actually know your data structures. Almost every big-tech design interview includes it — small, unambiguous, and reveals whether you can hit the O(1) requirement.
Why no design patterns fit
Modules 1-3 taught GoF patterns. NONE of them are the right answer here.
- Strategy? Not swapping algorithms.
- Factory? Not building polymorphic types.
- Observer? Nothing to observe.
- State? Only two states (present / absent).
This is a data-structure design problem, not a
class-composition design problem. Your design.md
should say this out loud. Reaching for a pattern to
"look sophisticated" is a junior signal.
The grader accepts "no pattern from modules 1-3 applies — this is a data-structures problem" as a valid answer for the Patterns section. Say so. Then defend your data-structure choice.
The DS choice
Three reasonable ways to do it:
- Plain list. get O(N), put O(N). Fails the O(1) requirement.
- Python's OrderedDict. get O(1), put O(1), and
move_to_endhandles the "promote" step. Fine in real code. But interviewers usually want to see you know WHAT OrderedDict does inside — hence... - Doubly-linked list + hash map. Manual implementation. O(1) both operations. Interview-classic.
Your design.md should pick one and defend it. For the tests, OrderedDict would work — but the "show you know the mechanics" answer is DLL + map.
The rule that matters
The DLL and the hash map must stay in sync. Every put that adds a key must add to both. Every eviction must remove from both. Every promote must reorder the DLL AND leave the map untouched.
The way to enforce this: every change goes through a
small number of helper methods (_add_after_head,
_unlink, _evict_tail). No self._map[key] = node
in one place and self._map.pop(key) scattered
elsewhere.
Why sentinels help
A DLL with dummy head and tail sentinels (nodes with
key=None, value=None, no meaning) makes edge cases
trivial. Every real node has BOTH a prev and a next —
no if node.prev is None special cases. The MRU is
head.next, the LRU is tail.prev.
Interviewers can spot right away whether you're using sentinels — cleaner code is a small signal that you've implemented this before.
Performance follow-ups to expect
- "How would you make this thread-safe?" — wrap all operations in a lock. Or use a lock-free CAS approach (harder, out of scope).
- "How would you add TTL (time-based expiry)?" — new mechanism (min-heap on expiry) alongside the LRU. Two eviction policies working together.
- "How would you scale it across machines?" — Redis. Consistent hashing. HLD territory.
Your design.md doesn't need to answer these, but a Trade-offs section that mentions "single-threaded and no TTL are known simplifications" scores points.