Expert: advanced patterns and interviews · Interview strategy and mock problems · topic 60 of 61 · 40 min
Mock problems: design-style data structures
Explanation
What a design-style question looks like
The interviewer describes a class and its methods: "Design a cache with get(key) and put(key, value), both O(1); when it is full, remove the least recently used key." You choose the data structures, write the class, and explain the cost of each method. No single structure does everything, so the answer is usually two structures working together.
LRU cache
A cache keeps recent answers so they can be returned quickly. When it is full, something must go. LRU (least recently used) removes the key that was used longest ago, because recently used data is likely to be used again. You need two things fast: find a key (a hash map does that in O(1)) and know the usage order, moving a key to "most recent" and removing the oldest (a doubly linked list does that in O(1), because a node can be unlinked when you hold a pointer to it).
The classic design is a dict from key to list node, plus a doubly linked list ordered from oldest to newest. get moves the node to the newest end. put inserts or updates at the newest end and, if over capacity, removes the node at the oldest end and its key from the dict. Python's collections.OrderedDict already combines both, so you can use it when the interviewer allows; be ready to explain the linked-list version too.
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity):
self.capacity = capacity
self.data = OrderedDict()
def get(self, key):
if key not in self.data:
return -1
self.data.move_to_end(key) # now the most recent
return self.data[key]
def put(self, key, value):
if self.capacity <= 0:
return
if key in self.data:
self.data.move_to_end(key)
self.data[key] = value
if len(self.data) > self.capacity:
self.data.popitem(last=False) # remove the least recent
c = LRUCache(2)
c.put(1, "a"); c.put(2, "b"); c.get(1); c.put(3, "c")
print(list(c.data)) # Output: [1, 3] (key 2 was least recently used)Hit counter and rate limiter
"Count the hits in the last 300 seconds" and "allow at most 100 requests per user per minute" are sliding windows over time. Keep a deque of timestamps; when a request arrives, pop timestamps older than the window from the left, then check the length. Each timestamp is added and removed once, so the cost is O(1) amortised per call. If many hits share a second, store (timestamp, count) pairs to save memory.
Time-based key-value store
set(key, value, timestamp) stores a value at a time, and get(key, timestamp) returns the value set at the latest time ≤ timestamp. If timestamps for each key arrive in increasing order, keep, for each key, a list of times and a list of values. They stay sorted, so get can use bisect_right(times, timestamp) − 1 to find the answer in O(log n). set is O(1) (append).
Insert, delete and getRandom in O(1)
A list gives a random item in O(1) (random.choice) but deleting from the middle is O(n). A dict finds an item in O(1) but cannot pick a random one fairly in O(1). Use both: a list of values and a dict from value to its index in the list. To delete, move the LAST value into the deleted slot, update its index in the dict, then pop the end of the list. Every operation is O(1).
Common mistakes
- In LRU, forgetting that get also makes a key "recently used". Fix: move it to the newest end on every get that finds it.
- In LRU, evicting before updating an existing key, so a key is removed for no reason. Fix: if the key exists, update it in place; evict only when the size goes over capacity.
- In the time store, using bisect_left, which misses a value set exactly at the asked time. Fix: bisect_right(times, t) − 1, and check that the result is ≥ 0.
- In getRandom-set, deleting the last element and then writing it back into its own (now missing) slot. Fix: handle "the value is the last element" correctly, or do the swap before popping, as in the solution.
- Not stating the cost of every method. Fix: finish with a short table: get O(1), put O(1), space O(capacity).
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Mock problems: design-style data structures · 1/8
This lesson covers design-style questions, where you build a small class with fast methods.
Prefer a video by a teacher? Find free videos on “LRU cache design hash map doubly linked list time based key value store interview” on YouTube ↗
In real life: Caching and rate limiting in web APIs
Busy apps, such as food delivery or travel booking apps, call many backend services. A small in-memory LRU cache in front of a slow service keeps the most recently requested results, for example restaurant menus, so repeated requests are fast; dedicated caches such as Redis can also be configured with LRU-style eviction. API gateways protect services with rate limits like "100 requests per minute per user", often with a sliding window of timestamps or a token bucket. The exact designs differ by company; the data structures in this lesson are the usual building blocks.
class RateLimiter {
constructor(limit, windowMs) {
this.limit = limit;
this.windowMs = windowMs;
this.hits = []; // timestamps, oldest first
}
allow(now) {
while (this.hits.length && this.hits[0] <= now - this.windowMs) this.hits.shift();
if (this.hits.length >= this.limit) return false;
this.hits.push(now);
return true;
}
}
const rl = new RateLimiter(2, 1000);
console.log([0, 100, 200, 1000, 1150].map((t) => rl.allow(t)));Practice (0/4 done)
Write a class LRUCache. LRUCache(capacity) creates an empty cache (capacity ≥ 0). get(key) returns the value for key, or -1 if it is not there, and marks the key as most recently used. put(key, value) inserts or updates the key and marks it as most recently used; if the cache then holds more than capacity keys, it removes the least recently used key. With capacity 0, put stores nothing. Both methods must be O(1) on average (you may use collections.OrderedDict).
Write a class TimeMap. set(key, value, timestamp) stores value for key at an integer timestamp; for each key, set is called with strictly increasing timestamps. get(key, timestamp) returns the value set for key at the LATEST time ≤ timestamp, or "" if there is none (also "" for an unknown key). set must be O(1) and get O(log n) using bisect.
In JavaScript, write a class LRUCache with constructor(capacity), get(key) (returns the value or -1, and marks the key as most recently used) and put(key, value) (inserts or updates, marks as most recent, and evicts the least recently used key when over capacity). Capacity 0 stores nothing. Use a Map: it remembers insertion order, so deleting and re-setting a key moves it to the end, and map.keys().next().value is the oldest key.
Write a class RandomizedSet with insert(val) (adds val; returns True if it was not present, else False), remove(val) (returns True if val was present and is now removed, else False), get_random() (returns a random current member; only called when the set is not empty) and size() (number of members). All four must be O(1) on average: keep a list of values and a dict from value to its index in the list.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Your study plan and the job huntInterview strategy and mock problems · same module
- Running the coding interviewInterview strategy and mock problems · same module
- Designing the data modelFull-Stack Development · Planning an app
- Big-O and searchingPython Programming · Algorithms, data structures and testing
- Caching and RedisBackend Development · Performance and reliability
- INSERT, UPDATE and DELETEBackend Development · Databases and SQL basics
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Which pair of structures gives an LRU cache O(1) get and put?
Answer: A hash map and a doubly linked list
The map finds a node in O(1); the doubly linked list moves and removes nodes in O(1).
In Python, which call removes the least recently used key from an OrderedDict used as an LRU cache?
Answer: popitem(last=False)
Keys are kept from oldest to newest; last=False removes from the oldest end.
A TimeMap key has times [1, 4, 9]. What index does bisect_right(times, 4) − 1 give, and why?
Answer: 1, the latest time ≤ 4
bisect_right puts 4 after the existing 4, at index 2; minus 1 gives index 1, the time 4 itself.
How does the O(1) RandomizedSet delete a value from the middle of its list?
Answer: Move the last value into the hole, update its index, and pop the end
Popping the end is O(1), and moving one value keeps the list without gaps.
How should you test get_random() so the test never fails by chance?
Answer: Check that it is a member of the set (or fix the seed)
Random results vary; membership and sizes are always true when the code is right.
What is a primary key?Related
Answer: A column whose value is unique for each row, used to identify it
A primary key (usually id) identifies exactly one row, so other tables can point to it.
What does it cost to insert a new node at the head of a singly linked list with n nodes?Related
About how many steps does binary search need for 1,000,000 sorted items?Related
Answer: About 20
Halving 1,000,000 about 20 times gets down to 1, because 2 to the power 20 is about a million.
From Big-O and searching
Which of these can be used as a key in a Python dict?Related
Answer: (1, 2)
Tuples of hashable items are hashable. Lists, sets and dicts can change, so they are unhashable.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 1 more topic in “Interview strategy and mock problems” before its exam: Your study plan and the job hunt.
- Module 12 exam: Interview strategy and mock problems · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- 61 topics left in the course before the final exam.
← Previous: Testing, edge cases and debugging under pressure