LRU Cache from scratch (HashMap + doubly linked list). — Cracked Java
// Data Structures & Algorithms · Hash Tables
SeniorCodingBig TechAmazonGoogleMeta

LRU Cache from scratch (HashMap + doubly linked list).

Problem. Design an LRU (Least Recently Used) cache supporting get(key) and put(key, value) in O(1) time. When put exceeds capacity, evict the least-recently-used entry. Both get and put count as "use."

The LinkedHashMap access-order trick is the idiomatic Java answer, but interviewers asking "from scratch" want to see the underlying data structure: a hash map + doubly-linked list.

Why these two structures together

  • The hash map gives O(1) lookup from key → node.
  • The doubly-linked list maintains recency order: most-recently-used at the head, least-recently-used at the tail. A doubly-linked list lets you unlink any node in O(1) (you have both neighbors), which a singly-linked list cannot.

On every access you unlink the node and re-insert it at the head. Eviction is "remove the tail." Both are constant-time pointer surgery.

Full implementation

public class LRUCache {

    private static final class Node {
        int key, value;
        Node prev, next;
        Node(int key, int value) { this.key = key; this.value = value; }
    }

    private final int capacity;
    private final Map<Integer, Node> map = new HashMap<>();
    private final Node head, tail;   // sentinels: head.next = MRU, tail.prev = LRU

    public LRUCache(int capacity) {
        this.capacity = capacity;
        head = new Node(0, 0);
        tail = new Node(0, 0);
        head.next = tail;
        tail.prev = head;
    }

    public int get(int key) {
        Node node = map.get(key);
        if (node == null) return -1;
        moveToFront(node);            // mark as most-recently-used
        return node.value;
    }

    public void put(int key, int value) {
        Node node = map.get(key);
        if (node != null) {           // update existing
            node.value = value;
            moveToFront(node);
            return;
        }
        if (map.size() == capacity) { // evict LRU (the node before the tail sentinel)
            Node lru = tail.prev;
            remove(lru);
            map.remove(lru.key);
        }
        Node fresh = new Node(key, value);
        map.put(key, fresh);
        addToFront(fresh);
    }

    // --- doubly-linked list helpers, all O(1) ---

    private void remove(Node n) {
        n.prev.next = n.next;
        n.next.prev = n.prev;
    }

    private void addToFront(Node n) {
        n.next = head.next;
        n.prev = head;
        head.next.prev = n;
        head.next = n;
    }

    private void moveToFront(Node n) {
        remove(n);
        addToFront(n);
    }
}

Complexity

OperationBestAverageWorstNote
getO(1)O(1)O(1)hash lookup + constant pointer updates
putO(1)O(1)O(1)lookup + at most one eviction

Space is O(capacity) — the map and list hold the same nodes.

Mark your status