LRU Cache

MediumDesign · Hash Map + Doubly Linked List🧩 Data Structure DesignLeetCode #146 ↗Updated

Problem

Build a key–value cache with a fixed capacity. get(key) returns the value or -1; put(key, value) inserts or updates. Both count as a use. When an insert would exceed capacity, first evict the least recently used key. Both operations must be O(1) on average.
Inputcap=2; put(1,1) put(2,2) get(1)Output1
Inputput(3,3) get(2)Output-1 (2 was LRU)
Inputput(4,4) get(1) get(3)Output-1, 3

1 ≤ capacity ≤ 3000  ·  0 ≤ key ≤ 104  ·  0 ≤ value ≤ 105  ·  ≤ 2·105 calls  ·  O(1) get/put

Try it yourself

Java
class LRUCache {
    public LRUCache(int capacity) {
        
    }

    public int get(int key) {
        
    }

    public void put(int key, int value) {
        
    }
}
Your code is saved in this browser.Submit on LeetCode ↗
Key insight
A hash map gives O(1) lookup but no order; a list gives order but O(n) lookup. Store list nodes in the map: a doubly linked list lets you unlink any node in O(1), so “move to front” and “evict from back” are both O(1).
PatternData Structure Design

Instinct Implement a class whose methods must all hit a time bound (often O(1)) - write each method's target cost, then glue a hash map to a structure that keeps order.

Memory hack Finder plus keeper - a hash map finds it fast, a list/heap/array keeps the order, and every method updates both.

Open the Data Structure Design guide →

Approaches

Brute ForceHashMap + Recency List (Linear Remove)

Time O(capacity) per opSpace O(capacity)

Values in a map, keys in a list from least to most recently used. Each use removes the key from the list and re-appends it; evict from the front. Finding the key in the list is a linear scan.

Java
class LRUCache {
    private final int capacity;
    private final Map<Integer, Integer> map = new HashMap<>();
    private final LinkedList<Integer> order = new LinkedList<>(); // front = LRU
    public LRUCache(int capacity) { this.capacity = capacity; }
    public int get(int key) {
        if (!map.containsKey(key)) return -1;
        order.remove(Integer.valueOf(key)); // O(n) search by value
        order.addLast(key);
        return map.get(key);
    }
    public void put(int key, int value) {
        if (map.containsKey(key)) order.remove(Integer.valueOf(key));
        else if (map.size() == capacity) map.remove(order.removeFirst());
        map.put(key, value);
        order.addLast(key);
    }
}

Easy to write, but every access scans the list — misses the O(1) requirement.

At a glance

LevelApproachTimeSpace
Brute ForceHashMap + Recency List (Linear Remove)O(capacity) per opO(capacity)
BetterLinkedHashMap (Access Order)O(1) avgO(capacity)
OptimalHashMap + Hand-built Doubly Linked ListO(1) avgO(capacity)
Common pitfalls
  • Evicting when put only updates an existing key.
  • Forgetting that get also refreshes recency.
  • Node without its key — eviction cannot clean the map.
Interview tips
  • Explain why each structure alone fails, then combine them.
  • Mention LinkedHashMap, then build it by hand.
  • Trace the capacity-2 example, drawing the list.
Pattern
Hash map + doubly linked list → O(1) lookup plus O(1) reordering (LRU, LFU, ordered sets).
My notes saved in this browser