Bird
Raised Fist0
Interview Prepcustom-data-structuresmediumAmazonFacebookGoogleMicrosoftBloomberg

LRU Cache

Choose your preparation mode4 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
</>
IDE
class LRUCache: def __init__(self, capacity: int): pass def get(self, key: int) -> int: pass def put(self, key: int, value: int) -> None: passpublic class LRUCache { public LRUCache(int capacity) {} public int get(int key) { return 0; } public void put(int key, int value) {} }class LRUCache { public: LRUCache(int capacity) {} int get(int key) { return 0; } void put(int key, int value) {} };class LRUCache { constructor(capacity) {} get(key) { return 0; } put(key, value) {} }
class LRUCache:
    def __init__(self, capacity: int):
        pass
    def get(self, key: int) -> int:
        pass
    def put(self, key: int, value: int) -> None:
        pass
public class LRUCache {
    public LRUCache(int capacity) {
    }
    public int get(int key) {
        return -1;
    }
    public void put(int key, int value) {
    }
}
class LRUCache {
public:
    LRUCache(int capacity) {
    }
    int get(int key) {
        return -1;
    }
    void put(int key, int value) {
    }
};
class LRUCache {
    constructor(capacity) {
    }
    get(key) {
        return -1;
    }
    put(key, value) {
    }
}
Coming soon
0/10
Common Bugs to Avoid
Wrong: get returns stale value or -1 incorrectly after put of existing keyput does not update value or usage order for existing keysIn put, if key exists, update value and move node to front of usage list
Wrong: Evicted wrong key after get operationget does not update usage order, so eviction removes wrong keyIn get, move accessed node to front of usage list before returning value
Wrong: Cache size exceeds capacity or no eviction occursEviction logic missing or incorrect when capacity exceededBefore inserting new key, remove tail node of usage list and delete from map if capacity reached
Wrong: TLE on large inputUsing list removal or linear scans in get/put causing O(n) per operationUse doubly linked list with hash map to achieve O(1) get and put
Test Cases
t1_01basic
Input["LRUCache cache = new LRUCache(2);","cache.put(1, 1);","cache.put(2, 2);","cache.get(1);","cache.put(3, 3);","cache.get(2);","cache.put(4, 4);","cache.get(1);","cache.get(3);","cache.get(4);"]
Expected[null,null,null,1,null,-1,null,-1,3,4]

Cache capacity is 2. After putting keys 1 and 2, get(1) returns 1 and makes key 1 most recently used. Putting key 3 evicts key 2. get(2) returns -1 because key 2 was evicted. Putting key 4 evicts key 1. get(1) returns -1, get(3) returns 3, get(4) returns 4.

t1_02basic
Input["LRUCache cache = new LRUCache(3);","cache.put(1, 10);","cache.put(2, 20);","cache.put(3, 30);","cache.get(2);","cache.put(4, 40);","cache.get(1);","cache.get(3);","cache.get(4);"]
Expected[null,null,null,null,20,null,-1,30,40]

Capacity 3 cache stores keys 1,2,3. get(2) returns 20 and makes key 2 most recently used. put(4,40) evicts least recently used key 1. get(1) returns -1, get(3) returns 30, get(4) returns 40.

t2_01edge
Input["LRUCache cache = new LRUCache(1);","cache.put(1, 100);","cache.get(1);","cache.put(2, 200);","cache.get(1);","cache.get(2);"]
Expected[null,null,100,null,-1,200]

Capacity 1 cache evicts immediately on new put. After put(1,100), get(1) returns 100. put(2,200) evicts key 1. get(1) returns -1, get(2) returns 200.

t2_02edge
Input["LRUCache cache = new LRUCache(2);","cache.get(42);"]
Expected[-1]

Getting a key that was never added returns -1.

t2_03edge
Input["LRUCache cache = new LRUCache(2);","cache.put(1, 10);","cache.put(1, 20);","cache.get(1);"]
Expected[null,null,20]

Putting the same key multiple times updates its value and usage order. get(1) returns updated value 20.

t2_04edge
Input["LRUCache cache = new LRUCache(2);","cache.put(1, 1);","cache.put(2, 2);","cache.put(3, 3);","cache.get(1);","cache.get(2);","cache.get(3);"]
Expected[null,null,null,null,-1,2,3]

Put keys until capacity, then put one more evicts least recently used key 1. get(1) returns -1, get(2) returns 2, get(3) returns 3.

t3_01corner
Input["LRUCache cache = new LRUCache(2);","cache.put(1, 1);","cache.put(2, 2);","cache.get(1);","cache.get(2);","cache.put(3, 3);","cache.get(1);","cache.get(2);","cache.get(3);"]
Expected[null,null,null,1,2,null,-1,2,3]

Tests greedy trap: evicting wrong key if not updating usage order on get. get(1) and get(2) update usage order; put(3) evicts key 1 as least recently used after get(1) and get(2).

t3_02corner
Input["LRUCache cache = new LRUCache(2);","cache.put(1, 1);","cache.put(2, 2);","cache.put(1, 10);","cache.put(3, 3);","cache.get(1);","cache.get(2);","cache.get(3);"]
Expected[null,null,null,null,null,10,-1,3]

Tests 0/1 confusion: put updates existing key value and usage order, so key 1 is most recent. put(3) evicts key 2. get(1) returns updated 10, get(2) returns -1.

t3_03corner
Input["LRUCache cache = new LRUCache(3);","cache.put(1, 1);","cache.put(2, 2);","cache.put(3, 3);","cache.get(2);","cache.put(4, 4);","cache.get(3);","cache.get(1);","cache.get(4);"]
Expected[null,null,null,null,2,null,3,-1,4]

Tests off-by-one errors in eviction: after get(2), key 2 is most recent; put(4) evicts key 1. get(1) returns -1, get(3) returns 3, get(4) returns 4.

t4_01performance
Input{"capacity":100,"operations":[{"op":"put","key":0,"value":0},{"op":"put","key":1,"value":10},{"op":"put","key":2,"value":20},{"op":"put","key":3,"value":30},{"op":"put","key":4,"value":40},{"op":"put","key":5,"value":50},{"op":"put","key":6,"value":60},{"op":"put","key":7,"value":70},{"op":"put","key":8,"value":80},{"op":"put","key":9,"value":90},{"op":"put","key":10,"value":100},{"op":"put","key":11,"value":110},{"op":"put","key":12,"value":120},{"op":"put","key":13,"value":130},{"op":"put","key":14,"value":140},{"op":"put","key":15,"value":150},{"op":"put","key":16,"value":160},{"op":"put","key":17,"value":170},{"op":"put","key":18,"value":180},{"op":"put","key":19,"value":190},{"op":"put","key":20,"value":200},{"op":"put","key":21,"value":210},{"op":"put","key":22,"value":220},{"op":"put","key":23,"value":230},{"op":"put","key":24,"value":240},{"op":"put","key":25,"value":250},{"op":"put","key":26,"value":260},{"op":"put","key":27,"value":270},{"op":"put","key":28,"value":280},{"op":"put","key":29,"value":290},{"op":"put","key":30,"value":300},{"op":"put","key":31,"value":310},{"op":"put","key":32,"value":320},{"op":"put","key":33,"value":330},{"op":"put","key":34,"value":340},{"op":"put","key":35,"value":350},{"op":"put","key":36,"value":360},{"op":"put","key":37,"value":370},{"op":"put","key":38,"value":380},{"op":"put","key":39,"value":390},{"op":"put","key":40,"value":400},{"op":"put","key":41,"value":410},{"op":"put","key":42,"value":420},{"op":"put","key":43,"value":430},{"op":"put","key":44,"value":440},{"op":"put","key":45,"value":450},{"op":"put","key":46,"value":460},{"op":"put","key":47,"value":470},{"op":"put","key":48,"value":480},{"op":"put","key":49,"value":490},{"op":"put","key":50,"value":500},{"op":"put","key":51,"value":510},{"op":"put","key":52,"value":520},{"op":"put","key":53,"value":530},{"op":"put","key":54,"value":540},{"op":"put","key":55,"value":550},{"op":"put","key":56,"value":560},{"op":"put","key":57,"value":570},{"op":"put","key":58,"value":580},{"op":"put","key":59,"value":590},{"op":"put","key":60,"value":600},{"op":"put","key":61,"value":610},{"op":"put","key":62,"value":620},{"op":"put","key":63,"value":630},{"op":"put","key":64,"value":640},{"op":"put","key":65,"value":650},{"op":"put","key":66,"value":660},{"op":"put","key":67,"value":670},{"op":"put","key":68,"value":680},{"op":"put","key":69,"value":690},{"op":"put","key":70,"value":700},{"op":"put","key":71,"value":710},{"op":"put","key":72,"value":720},{"op":"put","key":73,"value":730},{"op":"put","key":74,"value":740},{"op":"put","key":75,"value":750},{"op":"put","key":76,"value":760},{"op":"put","key":77,"value":770},{"op":"put","key":78,"value":780},{"op":"put","key":79,"value":790},{"op":"put","key":80,"value":800},{"op":"put","key":81,"value":810},{"op":"put","key":82,"value":820},{"op":"put","key":83,"value":830},{"op":"put","key":84,"value":840},{"op":"put","key":85,"value":850},{"op":"put","key":86,"value":860},{"op":"put","key":87,"value":870},{"op":"put","key":88,"value":880},{"op":"put","key":89,"value":890},{"op":"put","key":90,"value":900},{"op":"put","key":91,"value":910},{"op":"put","key":92,"value":920},{"op":"put","key":93,"value":930},{"op":"put","key":94,"value":940},{"op":"put","key":95,"value":950},{"op":"put","key":96,"value":960},{"op":"put","key":97,"value":970},{"op":"put","key":98,"value":980},{"op":"put","key":99,"value":990},{"op":"get","key":0},{"op":"get","key":50},{"op":"get","key":99}]}
⏱ Performance - must finish in 2000ms

Performance test with capacity=100 and 100 put operations followed by 3 get operations to ensure O(1) operations complete within 2 seconds.

Practice

(1/5)
1. Given the following code snippet for the optimal UndergroundSystem implementation, what is the value of travel_time when checkOut(1, "Leyton", 20) is called after checkIn(1, "Paradise", 3) and checkIn(2, "Leyton", 10)?
class Node:
    def __init__(self, id, station, time):
        self.id = id
        self.station = station
        self.time = time
        self.next = None

class UndergroundSystem:
    def __init__(self):
        self.head = None
        self.route_data = {}

    def checkIn(self, id: int, stationName: str, t: int) -> None:
        new_node = Node(id, stationName, t)
        new_node.next = self.head
        self.head = new_node

    def checkOut(self, id: int, stationName: str, t: int) -> None:
        prev = None
        curr = self.head
        while curr:
            if curr.id == id:
                travel_time = t - curr.time
                route = (curr.station, stationName)
                if route not in self.route_data:
                    self.route_data[route] = [0, 0]
                self.route_data[route][0] += travel_time
                self.route_data[route][1] += 1
                if prev:
                    prev.next = curr.next
                else:
                    self.head = curr.next
                return
            prev = curr
            curr = curr.next
easy
A. 10
B. 17
C. 7
D. 20

Solution

  1. Step 1: Trace checkIn calls

    First, checkIn(1, "Paradise", 3) adds Node(id=1, station="Paradise", time=3) at head. Then checkIn(2, "Leyton", 10) adds Node(id=2, station="Leyton", time=10) at head.
  2. Step 2: Trace checkOut(1, "Leyton", 20)

    Traverse linked list from head: Node with id=2 (not match), then Node with id=1 (match). travel_time = 20 - 3 = 17.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Check-in time for id=1 is 3, checkout time is 20, difference is 17 [OK]
Hint: Subtract check-in time from checkout time for travel_time [OK]
Common Mistakes:
  • Using wrong node's time due to linked list order
2. You need to design a data structure that supports adding intervals, removing intervals, and querying whether a given range is fully covered by the added intervals. Which approach guarantees efficient updates and queries with logarithmic time complexity per operation?
easy
A. Use a balanced binary search tree (e.g., TreeMap) to store and merge intervals, enabling logarithmic time operations.
B. Use a brute force list of intervals and scan all intervals for each operation.
C. Use dynamic programming to precompute coverage for all possible ranges.
D. Use a greedy algorithm that always merges intervals only when queried.

Solution

  1. Step 1: Understand the problem requirements

    The data structure must support add, remove, and query operations on intervals efficiently.
  2. Step 2: Evaluate approaches

    Brute force scanning is O(n) per operation, which is inefficient. DP is not suitable for dynamic interval updates. Greedy merging only on query delays merging and leads to inefficient queries. Balanced BST with interval merging maintains sorted intervals and merges overlapping ones, supporting O(log n) operations.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Balanced BST with merging -> O(log n) per operation [OK]
Hint: Balanced BST with merging enables efficient interval operations [OK]
Common Mistakes:
  • Thinking DP or greedy solves dynamic interval updates efficiently
3. The following code attempts to copy a linked list with random pointers using the optimal weaving approach. Identify the line that contains a subtle bug that can cause incorrect random pointer assignment or list corruption.
def copyRandomList(head):
    if not head:
        return None
    curr = head
    # Step 1: Insert copied nodes
    while curr:
        new_node = Node(curr.val, curr.next)
        curr.next = new_node
        curr = new_node.next
    # Step 2: Assign random pointers
    curr = head
    while curr:
        if curr.random:
            curr.next.random = curr.random  # Bug here
        curr = curr.next.next
    # Step 3: Separate lists
    curr = head
    copy_head = head.next
    copy_curr = copy_head
    while curr:
        curr.next = curr.next.next
        if copy_curr.next:
            copy_curr.next = copy_curr.next.next
        curr = curr.next
        copy_curr = copy_curr.next
    return copy_head
medium
A. Line assigning curr.next.random = curr.random
B. Line inserting copied nodes: curr.next = new_node
C. Line advancing curr in Step 1: curr = new_node.next
D. Line separating original and copied lists: curr.next = curr.next.next

Solution

  1. Step 1: Analyze random pointer assignment

    The line assigns curr.next.random = curr.random, but curr.random points to original nodes, not copied nodes.
  2. Step 2: Correct assignment

    It should be curr.next.random = curr.random.next to point to the copied node corresponding to curr.random.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Incorrect random pointer assignment breaks copied list correctness [OK]
Hint: Random pointer must point to copied node, not original [OK]
Common Mistakes:
  • Assigning random pointer directly to original node
  • Mixing original and copied nodes after weaving
  • Forgetting to advance curr by two nodes
4. What is the time complexity of the checkOut operation in the optimal UndergroundSystem implementation that uses a linked list for check-ins, assuming n is the number of active check-ins at the time of checkout?
medium
A. O(1) because the linked list head always points to the correct check-in node
B. O(n) due to updating route data hash map entries
C. O(log n) because the linked list is sorted by check-in time
D. O(n) because it may need to traverse the linked list to find the matching check-in node

Solution

  1. Step 1: Identify linked list traversal

    The checkOut method traverses the linked list from head to find the node with matching id, which can take up to O(n) time.
  2. Step 2: Analyze route data update

    Updating the hash map for route data is O(1) average, so it does not dominate complexity.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Traversal over n nodes dominates, so O(n) time [OK]
Hint: Linked list traversal dominates checkOut time [OK]
Common Mistakes:
  • Assuming O(1) because head is used directly
5. What is the time complexity of adding a number and then finding the median using the balanced two heaps approach with lazy deletion for a data stream of size n?
medium
A. O(log n) per addNum and O(log n) per findMedian
B. O(n) per addNum and O(1) per findMedian
C. O(log n) per addNum and O(1) per findMedian
D. O(1) per addNum and O(n) per findMedian

Solution

  1. Step 1: Analyze addNum complexity

    Adding a number involves pushing to a heap and balancing heaps, each O(log n) operations.
  2. Step 2: Analyze findMedian complexity

    Median is retrieved from the top of heaps without modification, O(1) time.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Insertion is O(log n), median retrieval O(1) [OK]
Hint: Heap push/pop are O(log n), median peek is O(1) [OK]
Common Mistakes:
  • Assuming sorting each query is O(log n)
  • Confusing addNum with findMedian complexity