Design
Expert Answer & Key Takeaways
LRU Cache
Requirements
- get(key): Return the value of the key if it exists, otherwise return -1.
- put(key, value): Update or insert the value. If the cache reaches capacity, evict the least recently used item before inserting a new one.
Goal
Examples
Level I: Brute Force (List of Pairs)
Intuition
[key, value] pairs. For every get or put operation, we iterate through the list to find if the key exists. This established a baseline for why we need more optimized structures.Detailed Dry Run
put(1,1), put(2,2), get(1)| Step | Operation | List State (Old to New) | Result |
|---|---|---|---|
| 1 | put(1,1) | [[1,1]] | null |
| 2 | put(2,2) | [[1,1], [2,2]] | null |
| 3 | get(1) | [[2,2], [1,1]] | 1 |
Level II: Intermediate (Built-in Ordered Map)
Intuition
LinkedHashMap in Java, OrderedDict in Python, or the standard Map in JS which preserves key insertion order) to implement LRU with minimal code.Detailed Dry Run
put(1,1), put(2,2), get(1)| Step | Operation | Map State (Keys) | LRU Logic |
|---|---|---|---|
| 1 | put(1,1) | {1} | Added 1 |
| 2 | put(2,2) | {1, 2} | Added 2 |
| 3 | get(1) | {2, 1} | Re-inserted 1 to end |
Level III: Optimal (HashMap + Deque)
Intuition
Detailed Dry Run
put(1,1), put(2,2), get(1)| Step | Operation | DLL Structure (Head <-> Tail) | Map State |
|---|---|---|---|
| 1 | put(1,1) | H <-> [1:1] <-> T | {1: node1} |
| 2 | put(2,2) | H <-> [2:2] <-> [1:1] <-> T | {1: n1, 2: n2} |
| 3 | get(1) | H <-> [1:1] <-> [2:2] <-> T | {1: n1, 2: n2} |
Design Tic-Tac-Toe
- A move is guaranteed to be valid and is placed on an empty block.
- Once a winning condition is reached, no more moves will be allowed.
- A player who succeeds in placing of their marks in a horizontal, vertical, or diagonal row wins the game.
Goal
move(row, col, player) must run in time complexity.Examples
Level I: Brute-Force Grid Scan
Intuition
Detailed Dry Run
n=3, move(0,0,1)| Step | Move | Grid State | Result |
|---|---|---|---|
| 1 | (0,0,1) | [[1,0,0],...] | 0 (No win yet) |
Level II: Optimized Grid Scan
Intuition
Detailed Dry Run
n=3, move(1,1,1)| Scan | Elements | Result |
|---|---|---|
| Row 1 | [0,1,0] | No Win |
| Col 1 | [0,1,0] | No Win |
| Diag 1 | [1,1,0] | No Win |
| Diag 2 | [0,1,0] | No Win |
Level III: Counter Arrays (Optimal)
Intuition
Detailed Dry Run
n=3, move(0,0,1)| Step | Move | Rows Count | Cols Count | Diag | Anti-Diag | Result |
|---|---|---|---|---|---|---|
| 1 | (0,0,1) | [1,0,0] | [1,0,0] | 1 | 0 | 0 |
| 2 | (1,1,1) | [1,1,0] | [1,1,0] | 2 | 0 | 0 |
| 3 | (2,2,1) | [1,1,1] | [1,1,1] | 3 | 0 | 1 (Win!) |
Implement Stack using Queues
push, top, pop, and empty).Examples
Level I: Two Queues (Push O(1))
Intuition
push is to q1. For pop, we move all elements except the last one from q1 to q2, then the last one is the stack's top.Detailed Dry Run
push(1), push(2), pop()\n| Step | Op | Q1 | Q2 | Result |\n| :--- | :--- | :--- | :--- | :--- |\n| 1 | push(1) | [1] | [] | null |\n| 2 | push(2) | [1, 2] | [] | null |\n| 3 | pop() | [] | [1] | 2 |Level II: Two Queues (Pop/Top O(1))
Intuition
pop and top , we do the heavy lifting during push. When pushing x, we add it to q2, then move everything from q1 to q2, then swap names. This keeps q1 in LIFO order.Detailed Dry Run
| Op | Q1 | Q2 | Action |
|---|---|---|---|
push(1) | [1] | [] | Simple add |
push(2) | [] | [2, 1] | Add 2 to Q2, then move 1 from Q1 to Q2 |
pop() | [1] | [] | pop from Q1 |
Level III: One Queue (Optimal)
Intuition
push, we add the element and rotate the queue times so the newly added element becomes the front of the queue.Detailed Dry Run
[1, 2] -> push(3) -> [1, 2, 3] -> Rotate 1: [2, 3, 1] -> Rotate 2: [3, 1, 2]| Step | Queue | Action |
|---|---|---|
| 1 | [1,2] | Initial |
| 2 | [1,2,3] | Add 3 to back |
| 3 | [2,3,1] | Poll 1, Add to back |
| 4 | [3,1,2] | Poll 2, Add to back |
| Result: 3 is at front (LIFO) |
Implement Queue using Stacks
push, peek, pop, and empty).Examples
Level I: Two Stacks (Push O(N))
Intuition
Detailed Dry Run
Level II: Two Stacks (Pop O(N))
Intuition
push , just push to s1. For pop and peek, if s1 has elements, move everything to s2, remove/view the top, then move everything back to s1 to maintain order. This establishes a baseline for amortized optimization.Detailed Dry Run
| Op | S1 | S2 | Action |
|---|---|---|---|
push(1) | [1] | [] | |
push(2) | [1, 2] | [] | |
pop() | [] | [1, 2] | Move S1 to S2, pop 1, move back |
| Result: 1 |
Level III: Two Stacks (Amortized O(1))
Intuition
inStack for push and outStack for pop. Transfer only when outStack is empty. This way, each element is moved at most twice (once into inStack, once into outStack), leading to amortized time.Detailed Dry Run
| Op | In | Out | Action |
|---|---|---|---|
push(1) | [1] | [] | |
push(2) | [1, 2] | [] | |
pop() | [] | [2] | Move S1 to S2, return 1 |
| Result: 1 |
Design Hit Counter
Examples
Level I: Queue of Timestamps
Intuition
getHits, remove all timestamps from the front that are older than 300 seconds.Detailed Dry Run
Level II: Map of Count by Second
Intuition
HashMap<Timestamp, Count>. This is more space-efficient if there are many hits at the same time.Detailed Dry Run
Level III: Scalable Circular Array (Optimal)
Intuition
Detailed Dry Run
Design Circular Queue
Level I: Array with Front/Rear Pointers
Intuition
head and tail. Use modulo arithmetic to wrap around the array. We use a size variable to easily distinguish between empty and full states.Detailed Dry Run
| Op | Array | Head | Tail | Size |
|---|---|---|---|---|
init(3) | [0,0,0] | 0 | -1 | 0 |
enq(1) | [1,0,0] | 0 | 0 | 1 |
enq(2) | [1,2,0] | 0 | 1 | 2 |
deq() | [1,2,0] | 1 | 1 | 1 |
Level II: Linked List Implementation
Intuition
k.Detailed Dry Run
Min Stack
Level I: Brute Force (Scan on getMin)
Intuition
getMin() is called, iterate through the entire stack to find the minimum element.Detailed Dry Run
Level II: Two Stacks (Standard Optimality)
Intuition
getMin() is .Detailed Dry Run
[5] | [5] | |
| [5, 3] | [5, 3] | 3 < 5, so push 3 |
| pop() | [5] | both pop |Level III: Optimized Space (Single Stack with Min Delta)
Intuition
Detailed Dry Run
Design Add and Search Words
. where a dot can match any letter.Complexity
addWord: where is word length.search: in worst case due to wildcards, but typically fast with Trie pruning.
Examples
Level I: Set of Words
Intuition
HashSet. For search, if it contains no dots, do an lookup. If it contains dots, iterate through all words in the set () and check if they match the pattern.Level II: HashMap of Lengths
Intuition
HashMap<Integer, Set<String>>. When searching, we only check words of the exact same length as the input, significantly reduced the comparisons.Detailed Dry Run
Level III: Trie (Prefix Tree) with DFS
Intuition
Design Underground System
Requirement
checkIn(id, stationName, t)checkOut(id, stationName, t)getAverageTime(startStation, endStation)
Examples
Level I: Single HashMap with List (Brute Force)
Intuition
Detailed Dry Run
Level II: String Concatenation Keys
Intuition
start+"->"+end as keys in a single HashMap. Store lists of travel durations and compute averages on the fly.Level III: Two HashMaps
Intuition
checkInMap to store id -> (station, time). Use routeMap to store (start, end) -> (totalTime, count). Checkout triggers a look-up in checkInMap, calculates the duration, and updates routeMap.Design Snake Game
Logic
- Move the head.
- Check boundaries and self-collision.
- If head hits food, increase length (don't remove tail).
- Otherwise, move forward (remove tail).
Examples
Level II: Deque only (Body only scan)
Intuition
Level III: Deque + HashSet
Intuition
Deque to store the coordinates of the snake's body (head at front, tail at back). Use a HashSet of encoded coordinates (r * width + c) for self-collision check.LFU Cache
Requirement
- get(key): Return value if exists, update frequency.
- put(key, value): Insert/update value. If at capacity, evict the least frequently used item. If there's a tie, evict the least recently used.
Examples
Level I: Brute Force Scan
Intuition
List or Map. For every put at capacity, iterate through the entire collection to find the item with the minimum frequency and the oldest access time. This is per operation but extremely simple.Detailed Dry Run
Level II: Priority Queue (O(log N))
Intuition
PriorityQueue to store entries sorted by frequency, and then by access time (tie-breaker). While push and pop are , it is simpler to implement than the DLL version.Level III: Map Frequency to Doubly Linked List
Intuition
minFreq variable. Use one map for key -> node and another for freq -> DLL of nodes. When a key is accessed, move it from count DLL to count+1 DLL. If count DLL becomes empty and count == minFreq, increment minFreq. Eviction happens at freqMap[minFreq].tail.prev.Insert Delete GetRandom O(1) - Duplicates allowed
insert, remove, and getRandom in time, where duplicates are allowed.Strategy
List of values (for random access) with a HashMap<Value, Set<Indices>> (for removal).Examples
Level I: Simple List with Linear Scan
Intuition
ArrayList to store values. For insert, append to the end. For remove, find the first occurrence using a linear scan and remove it. For getRandom, pick a random index in .Detailed Dry Run
Level II: Map of Count + Randomized Index
Intuition
Map<Value, Frequency> and a List<Value>. When removing, find the first occurrence in the List and replace it with the last element. This is for remove but for others.Level III: HashMap of Sets + Array
Intuition
All O(1) Data Structure
Requirement
inc(key): Increments the count ofkeyby one.dec(key): Decrements the count ofkeyby one.getMaxKey(): Returns one of the keys with the maximum count.getMinKey(): Returns one of the keys with the minimum count.
Examples
Level I: Brute Force Map Scan
Intuition
HashMap<String, Integer>. For getMaxKey and getMinKey, iterate through the entire map to find the max/min. This is for updates but for queries.Detailed Dry Run
Level II: Map of Counts + Set of Keys (Lazy Removal)
Intuition
Map<String, Integer> keyCount and Map<Integer, Set<String>> countKeys. When incrementing, move the key from its current set to the set for count + 1. This is but slightly slower than the DLL version due to hash operations.Level III: HashMap + Doubly Linked List of Sets
Intuition
HashMap to store key -> count. Use a DLL where each node represents a specific count and contains a Set of all keys with that count. This allows movement between nodes when incrementing/decrementing.Design Twitter
Core Operations
postTweet(userId, tweetId)getNewsFeed(userId)follow(followerId, followeeId)unfollow(followerId, followeeId)
Scaling Hint
Examples
Level I: Map of Lists (Brute Force Sort)
Intuition
Map<UserId, List<TweetId>>. For getNewsFeed, collect all tweets from the user and their followees into a single list, sort them by timestamp, and return the top 10.Detailed Dry Run
Level II: Global List + Filtering
Intuition
getNewsFeed(userId) is called, iterate backwards through the list and pick the first 10 tweets that were posted by the user or their followees.Level III: Map + Heap (Pull-based Feed)
Intuition
User class containing a Set of followees and a LinkedList of tweets. For getNewsFeed, push the first tweet of each user in the followee list into a Max-Heap (sorted by timestamp), and then standard merge-k-sorted-lists logic.Design Phone Directory
get, check, and release operations for a fixed set of numbers.Requirement
get(): Returns an available number or -1.check(num): Returns true if a number is available.release(num): Makes a number available again.
Examples
Level I: Boolean Array (Simple Scan)
Intuition
boolean[] of size maxNumbers to track which numbers are used. For get, scan the array from the beginning to find the first false index. This is for get and for others.Detailed Dry Run
Level II: BitSet (Space Optimized)
Intuition
BitSet to track availability. Finding the next available number is in the bitset, but space is reduced dramatically.Level III: Queue + HashSet
Intuition
Queue to store available numbers (for get) and a HashSet to store current available numbers (for check). When release is called, add back to both if it wasn't already available.Data Stream as Disjoint Intervals
Requirement
addNum(val): Add integer to the stream.getIntervals(): Return the summary as a list of intervals.
Examples
Level I: List of Numbers + Rebuild on Query
Intuition
Set to handle duplicates. For getIntervals, convert the set to a sorted list and iterate through it to form intervals whenever numbers are not consecutive.Detailed Dry Run
Level II: Sorted List (Manual Merging)
Intuition
addNum, find the insertion spot and check if it can merge with neighbor intervals. This is due to list shifting.Level III: Balanced BST / TreeMap
Intuition
TreeMap to store intervals as start -> end. When a new number x is added, find the interval that ends just before it and the one that starts just after it. Merge them if possible.Range Sum Query 2D - Mutable
Requirement
NumMatrix(matrix): Initializes the data structure.update(row, col, val): Updates the value at(row, col)toval.sumRegion(row1, col1, row2, col2): Returns the sum of elements within the specified rectangle.
Examples
Level I: Row-wise Prefix Sums
Intuition
sumRegion, iterate through each row in the range and compute the sum of that row's segment in using the prefix sums. This makes update and query .Detailed Dry Run
Level II: 2D Binary Indexed Tree (BIT)
Intuition
update and query both take time.Level III: 2D Segment Tree (Quadtree based)
Intuition
update and query take . Generally more powerful but harder to implement than BIT.Snapshot Array
SnapshotArray that supports the following operations:Requirement
SnapshotArray(length): Initializes an array-like data structure with the given length, where each element is initially 0.set(index, val): Sets the element at the givenindexto beval.snap(): Takes a snapshot of the array and returns thesnap_id.get(index, snap_id): Returns the value at the givenindex, corresponding to the givensnap_id.
Examples
Level I: Full Array Copy on Snap
Intuition
snap() is called, we create a full copy of the current array and store it in a List<int[]>. This is for set and get, but for snap.Detailed Dry Run
Level II: Versioned Map (HashMap per index)
Intuition
Map<Integer, Integer>[] arr. Each index has a map mapping snap_id -> value. This is simpler but takes more space for small changes.Level III: List of Lists (Binary Search Optimization)
Intuition
(snap_id, value) pairs. When get(index, snap_id) is called, perform a binary search (lower bound) on the list to find the value at or before that snap_id.Design Bounded Blocking Queue
Examples
Level I: Synchronized Methods with wait/notify
Intuition
synchronized on enqueue and dequeue. Inside each, use a while loop to wait() if the condition (full or empty) isn't met, and notifyAll() after making a change. This is the classic Java threading building block.Detailed Dry Run
Level II: Semaphores
Intuition
Semaphore fill = new Semaphore(0) and Semaphore drain = new Semaphore(cap). enqueue calls drain.acquire() and fill.release(). dequeue does the reverse.Level III: Condition Variables (Producer-Consumer)
Intuition
ReentrantLock with two Condition variables (e.g., notEmpty, notFull). Threads wait on these conditions when the queue is empty or at capacity, and signal each other when appropriate.Design In-Memory File System
ls, mkdir, addContentToFile, and readContentFromFile.Examples
Level I: Map of Full Paths
Intuition
Map<String, String> where the key is the full path and the value is the content. For ls, iterate through keys to find children. This is simple but slow for deep trees.Level III: Trie-based Tree Structure
Intuition
Map<String, Node> of children. This allows for efficient path traversal.Design Browser History
Level I: Two Stacks
Intuition
Detailed Dry Run
Level III: Single Array with Pointer (Optimal)
Intuition
visit and simply move the index on back / forward.Detailed Dry Run
Design Food Rating System
Level III: Multi-Index with Sorted Sets (Optimal)
Intuition
food -> (cuisine, rating) and another to map cuisine -> SortedSet of (rating, food). SortedSet handles lexicographical tie-breaks.Detailed Dry Run
Design Movie Rental System
Level III: Multi-Index Sorted Sets (Optimal)
Intuition
available[movie] -> SortedSet(price, shop), rented -> SortedSet(price, shop, movie), and priceMap[shop, movie] -> price.Detailed Dry Run
Course4All Technical Board
Verified ExpertSenior Software Engineers & Algorithmic Experts
Our DSA content is authored and reviewed by engineers from top tech firms to ensure optimal time and space complexity analysis.
Found an issue or have a suggestion?
Help us improve! Report bugs or suggest new features on our Telegram group.