Bird
Raised Fist0
Interview Prepchallenge-problemshardAmazonGoogleFacebook

LFU 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
📋
Problem

Imagine designing a caching system for a high-traffic website where you want to keep the most frequently accessed items readily available to minimize latency and server load.

Design and implement a data structure for Least Frequently Used (LFU) cache. It should support the following operations: get(key) and put(key, value). get(key) - Get the value of the key if it exists in the cache, otherwise return -1. put(key, value) - Update or insert the value if the key is not already present. When the cache reaches its capacity, it should invalidate and remove the least frequently used key before inserting a new item. If there is a tie in frequency, the least recently used key should be evicted.

0 ≤ capacity ≤ 10^50 ≤ key, value ≤ 10^9At most 2 * 10^5 calls will be made to get and put.
Edge cases: capacity = 0 → all puts should not store anything, all gets return -1put with existing key → should update value and frequencymultiple keys with same frequency → evict least recently used among them
</>
IDE
class LFUCache: def __init__(self, capacity: int): pass def get(self, key: int) -> int: pass def put(self, key: int, value: int) -> None: passpublic class LFUCache { public LFUCache(int capacity) {} public int get(int key) { return 0; } public void put(int key, int value) {} }class LFUCache { public: LFUCache(int capacity) {} int get(int key) { return 0; } void put(int key, int value) {} };class LFUCache { constructor(capacity) {} get(key) { return -1; } put(key, value) {} }
class LFUCache:
    def __init__(self, capacity: int):
        # Write your solution here
        pass
    def get(self, key: int) -> int:
        pass
    def put(self, key: int, value: int) -> None:
        pass
public class LFUCache {
    public LFUCache(int capacity) {
        // Write your solution here
    }
    public int get(int key) {
        return -1;
    }
    public void put(int key, int value) {
        // Write your solution here
    }
}
class LFUCache {
public:
    LFUCache(int capacity) {
        // Write your solution here
    }
    int get(int key) {
        return -1;
    }
    void put(int key, int value) {
        // Write your solution here
    }
};
class LFUCache {
    constructor(capacity) {
        // Write your solution here
    }
    get(key) {
        return -1;
    }
    put(key, value) {
        // Write your solution here
    }
}
Coming soon
0/9
Common Bugs to Avoid
Wrong: Evicts wrong key when multiple keys have same frequencyEviction logic ignores recency tie-break and evicts any LFU key arbitrarilyMaintain recency order within each frequency bucket using a linked list and evict the least recently used key among LFU keys
Wrong: get returns outdated value after put updates existing keyput does not update value or frequency for existing keysIn put, if key exists, update its value and increment frequency and recency accordingly
Wrong: Cache stores items despite zero capacity, get returns values instead of -1No explicit check for zero capacity in put and getReturn immediately in put if capacity == 0 and always return -1 in get if capacity == 0
Wrong: Solution times out on large inputEviction implemented by scanning all keys or sorting on each putUse frequency buckets and doubly linked lists to achieve O(1) average time per operation
Wrong: Frequency updates off by one causing wrong eviction or get resultsFrequency increment logic is incorrect or missing on get or putIncrement frequency exactly once per get or put for existing keys
Test Cases
t1_01basic
Input{"capacity":2,"operations":["put","put","get","put","get","get","put","get","get","get"],"arguments":[[1,1],[2,2],[1],[3,3],[2],[3],[4,4],[1],[3],[4]]}
Expected[null,null,1,null,-1,3,null,-1,3,4]

Initially cache is empty. After put(1,1) and put(2,2), cache has keys 1 and 2 with frequency 1. get(1) returns 1 and increases frequency of key 1 to 2. put(3,3) evicts key 2 because it has frequency 1 which is least. get(2) returns -1 because key 2 was evicted. get(3) returns 3 and increases frequency of key 3 to 2. put(4,4) evicts key 1 because keys 1 and 3 have frequency 2 but key 1 is least recently used. get(1) returns -1. get(3) returns 3. get(4) returns 4.

t1_02basic
Input{"capacity":3,"operations":["put","put","put","get","put","get","get","get"],"arguments":[[2,2],[1,1],[3,3],[2],[4,4],[1],[3],[4]]}
Expected[null,null,null,2,null,-1,3,4]

Cache capacity 3. After put(2,2), put(1,1), put(3,3), all keys have freq=1. get(2) returns 2 and freq(2)=2. put(4,4) evicts key 1 (freq=1, least recently used among freq=1). get(1) returns -1 (evicted). get(3) returns 3 freq=2. get(4) returns 4 freq=2.

t2_01edge
Input{"capacity":0,"operations":["put","get","put","get"],"arguments":[[1,1],[1],[2,2],[2]]}
Expected[null,-1,null,-1]

Capacity zero means cache cannot store any items. All puts do nothing, all gets return -1.

t2_02edge
Input{"capacity":1,"operations":["put","put","get","put","get"],"arguments":[[1,10],[1,20],[1],[2,30],[1]]}
Expected[null,null,20,null,-1]

Single element cache. put(1,10), then put(1,20) updates value and frequency. get(1) returns updated 20. put(2,30) evicts key 1. get(1) returns -1.

t2_03edge
Input{"capacity":2,"operations":["put","put","put","get","get"],"arguments":[[1,1],[2,2],[3,3],[1],[2]]}
Expected[null,null,null,-1,2]

After inserting keys 1 and 2, inserting key 3 evicts key 1 (freq=1, least recently used). get(1) returns -1, get(2) returns 2.

t3_01corner
Input{"capacity":2,"operations":["put","put","get","put","get","get"],"arguments":[[1,1],[2,2],[1],[3,3],[1],[3]]}
Expected[null,null,1,null,-1,3]

Greedy trap test: naive eviction by recency only. After get(1), freq(1)=2, freq(2)=1. put(3,3) evicts key 2 (lowest freq). get(1) returns -1 if eviction wrong, else 1. get(3) returns 3.

t3_02corner
Input{"capacity":2,"operations":["put","put","put","get","get","get"],"arguments":[[1,1],[2,2],[1,10],[1],[2],[3]]}
Expected[null,null,null,10,2,-1]

0/1 vs unbounded confusion test: put with existing key updates value and frequency. put(1,10) updates key 1's value and freq. get(1) returns 10, get(2) returns 2, get(3) returns -1.

t3_03corner
Input{"capacity":3,"operations":["put","put","put","get","get","put","get","get","get"],"arguments":[[1,1],[2,2],[3,3],[1],[2],[4,4],[1],[3],[4]]}
Expected[null,null,null,1,2,null,-1,3,4]

Off-by-one frequency update test: After get(1) and get(2), freq(1)=2, freq(2)=2, freq(3)=1. put(4,4) evicts key 3 (lowest freq). get(1) returns -1 if off-by-one error, else 1. get(3) returns 3 or -1 accordingly.

t4_01performance
Input{"capacity":100,"operations":["put","put","put","put","put","put","put","put","put","put","get","get","get","get","get","get","get","get","get","get","put","put","put","put","put","put","put","put","put","put","get","get","get","get","get","get","get","get","get","get","put","put","put","put","put","put","put","put","put","put","get","get","get","get","get","get","get","get","get","get","put","put","put","put","put","put","put","put","put","put","get","get","get","get","get","get","get","get","get","get","put","put","put","put","put","put","put","put","put","put","get","get","get","get","get","get","get","get","get","get","put","put","put","put","put","put","put","put","put","put","get","get","get","get","get","get","get","get","get","get"],"arguments":[[1,1],[2,2],[3,3],[4,4],[5,5],[6,6],[7,7],[8,8],[9,9],[10,10],[1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11,11],[12,12],[13,13],[14,14],[15,15],[16,16],[17,17],[18,18],[19,19],[20,20],[11],[12],[13],[14],[15],[16],[17],[18],[19],[20],[21,21],[22,22],[23,23],[24,24],[25,25],[26,26],[27,27],[28,28],[29,29],[30,30],[21],[22],[23],[24],[25],[26],[27],[28],[29],[30],[31,31],[32,32],[33,33],[34,34],[35,35],[36,36],[37,37],[38,38],[39,39],[40,40],[31],[32],[33],[34],[35],[36],[37],[38],[39],[40],[41,41],[42,42],[43,43],[44,44],[45,45],[46,46],[47,47],[48,48],[49,49],[50,50],[41],[42],[43],[44],[45],[46],[47],[48],[49],[50],[51,51],[52,52],[53,53],[54,54],[55,55],[56,56],[57,57],[58,58],[59,59],[60,60],[51],[52],[53],[54],[55],[56],[57],[58],[59],[60]]}
⏱ Performance - must finish in 2000ms

Large test with 100 capacity and 200 operations to verify O(1) average time complexity per operation within 2 seconds.

Practice

(1/5)
1. You need to design a data structure that supports incrementing and decrementing string keys, and retrieving a key with the maximum or minimum count, all in constant time. Which approach best guarantees O(1) time complexity for all these operations?
easy
A. Using a balanced binary search tree keyed by counts to maintain order of keys
B. Using a hash map to store counts and scanning all keys to find max/min when requested
C. Maintaining a doubly linked list of buckets, each bucket holding keys with the same count, plus hash maps for quick access
D. Using a priority queue to track max and min keys with lazy updates

Solution

  1. Step 1: Understand operation requirements

    Increment, decrement, getMaxKey, and getMinKey must all be O(1).
  2. Step 2: Evaluate approaches

    Hash map + linear scan (B) is O(n) for max/min. Balanced BST (C) and priority queue (D) have O(log n) operations. Doubly linked list with buckets and hash maps (A) supports O(1) updates and retrievals.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Bucket list + hash maps enable O(1) increments, decrements, and max/min retrievals [OK]
Hint: Bucket list + hash maps enable O(1) max/min updates [OK]
Common Mistakes:
  • Thinking balanced BST or priority queue can achieve O(1) for all operations
2. You need to design a system that stores a fixed number of key-value pairs and supports fast retrieval and insertion. When the capacity is exceeded, the system must evict the least recently accessed item. Which data structure approach best guarantees O(1) time complexity for both retrieval and insertion while maintaining the eviction policy?
easy
A. Use a simple list to store keys in order of usage and a hash map for key-value pairs.
B. Use a queue to store keys and a hash map for values.
C. Use a hash map combined with a doubly linked list to track usage order.
D. Use a balanced binary search tree keyed by access timestamps.

Solution

  1. Step 1: Understand the eviction policy

    The system must evict the least recently accessed item, which requires tracking usage order efficiently.
  2. Step 2: Identify data structure supporting O(1) operations

    A hash map provides O(1) access to values, and a doubly linked list allows O(1) updates to usage order by moving nodes.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Hash map + doubly linked list is the classic LRU cache design [OK]
Hint: LRU needs O(1) access and update of usage order [OK]
Common Mistakes:
  • Using list causes O(n) removal
  • Queue can't reorder on access
  • BST adds O(log n) overhead
3. Examine the following buggy code snippet from the AllOne data structure. Which line contains the subtle bug that causes keys to remain in multiple buckets after incrementing, leading to incorrect max/min results?
def inc(self, key: str) -> None:
    if key not in self.key_to_bucket:
        if 1 not in self.count_to_bucket:
            new_bucket = Bucket(1)
            self._insert_bucket_after(new_bucket, self.head)
        self.count_to_bucket[1].keys.add(key)
        self.key_to_bucket[key] = self.count_to_bucket[1]
    else:
        curr_bucket = self.key_to_bucket[key]
        next_count = curr_bucket.count + 1
        if next_count not in self.count_to_bucket:
            new_bucket = Bucket(next_count)
            self._insert_bucket_after(new_bucket, curr_bucket)
        self.count_to_bucket[next_count].keys.add(key)
        self.key_to_bucket[key] = self.count_to_bucket[next_count]
        # BUG: Missing removal of key from old bucket
        # curr_bucket.keys.remove(key)
        if len(curr_bucket.keys) == 0:
            self._remove_bucket(curr_bucket)
medium
A. Line removing key from old bucket's keys set (commented out)
B. Line updating key_to_bucket mapping
C. Line removing empty bucket if keys set is empty
D. Line adding key to new bucket's keys set

Solution

  1. Step 1: Identify key movement between buckets

    When incrementing, key must be removed from old bucket's keys set.
  2. Step 2: Locate missing removal

    The commented out line that removes the key from old bucket is missing, causing key to appear in multiple buckets.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Without removal, max/min queries become incorrect due to duplicate keys [OK]
Hint: Key must be removed from old bucket after increment [OK]
Common Mistakes:
  • Forgetting to remove key from old bucket after increment/decrement
4. What is the time complexity of performing n operations (push, pop, increment) on the optimal CustomStack implementation that uses a lazy increment array?
medium
A. O(n * k), where k is the increment size
B. O(n + k), where k is the maximum increment size
C. O(n log n), due to increment propagation
D. O(n), total for all operations combined

Solution

  1. Step 1: Analyze push and pop operations

    Each push and pop is O(1) amortized, as they add or remove one element.
  2. Step 2: Analyze increment operation

    Increment only updates inc array at one index, O(1) per call. Propagation happens lazily during pop, each element's increment propagated once.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    All n operations combined run in O(n) total time [OK]
Hint: Lazy increments make all operations O(1) amortized, total O(n) [OK]
Common Mistakes:
  • Assuming increment is O(k) per call
  • Confusing total time with per-operation time
5. Suppose the Twitter design is extended so that users can post the same tweet multiple times (tweet reuse allowed). Which modification to the optimal approach is necessary to correctly handle this scenario?
hard
A. Allow multiple TweetNode instances with the same tweetId but different timestamps in the linked list and heap.
B. Use a hash set to track unique tweetIds per user to avoid duplicates in the feed.
C. Modify the heap to store tweetIds only once, ignoring repeated posts of the same tweetId.
D. Disallow posting duplicate tweetIds by rejecting posts with existing tweetIds.

Solution

  1. Step 1: Understand tweet reuse impact

    Allowing duplicate tweetIds means multiple posts with same id but different times must be treated as distinct tweets.
  2. Step 2: Adjust data structures

    Each post creates a new TweetNode with unique timestamp; heap and linked lists handle duplicates naturally by time ordering.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Multiple nodes with same tweetId but different times must be stored separately [OK]
Hint: Treat each post as unique by timestamp, even if tweetId repeats [OK]
Common Mistakes:
  • Trying to deduplicate tweets in feed incorrectly
  • Rejecting duplicate posts unnecessarily