LinkedHashMap and building an LRU cache
LinkedHashMap internals, insertion and access order, Java 21 SequencedMap operations, and LRU cache implementations with LinkedHashMap and a HashMap plus doubly linked list.
On this page
🧵 What is LinkedHashMap?
LinkedHashMap is a HashMap with a doubly linked list threaded through its entries. You still get expected O(1) key lookup, but iteration follows a predictable order: either insertion order, or access order when you opt in. If you put A, B, C, then read A in access-order mode, the order becomes B, C, A.
Think of it as a hotel key rack with a fast lookup table behind it. The front desk can find room 418 by room number, but it also keeps the keys in a visible left-to-right order. That second order is what turns a plain map into a small LRU cache.
What you’ll learn
- Explain how
LinkedHashMap.Entryextends **HashMap.Node** withbeforeandafterlinks - Choose between insertion order and
accessOrder = true, including whygetcan move an entry - Use
afterNodeAccess,afterNodeInsertionandremoveEldestEntryto explain eviction hooks - Apply Java 21 **
SequencedMap** methods such asfirstEntry,pollFirstEntry,putFirst,putLastandreversed - Implement an LRU cache with a short
LinkedHashMapsubclass and with a HashMap plus doubly linked list - Discuss thread-safety, Caffeine, W-TinyLFU, expiry and size-by-weight limits without overselling LRU
For this lesson, we'll focus on how LinkedHashMap extends HashMap.Node, how the before and after links maintain insertion order or access order, the hooks afterNodeAccess, afterNodeInsertion and removeEldestEntry, Java 21 SequencedMap, and two LRU implementations: the short library version and the interview version you build with a HashMap plus a doubly linked list. Interviewers love this because the API is tiny, but the details reveal whether you can connect a map, a list and an eviction policy.
Below the line (out of scope)
- Hash table buckets, resize and treeification in full: How HashMap works
- Concurrent map internals: Hashtable, synchronizedMap and the concurrent maps
- General collection choice: Choosing the right collection
- Production-grade cache design: mentioned here, but covered later in system design lessons
The 30-second answer
LinkedHashMap subclasses HashMap and stores entries as nodes that also have before and after pointers. The map has head and tail fields, so iteration walks the linked list, not the bucket table. By default it keeps insertion order; with the (initialCapacity, loadFactor, true) constructor it keeps access order, and successful reads such as get move the entry to the tail through afterNodeAccess. After inserting a new entry, afterNodeInsertion can call removeEldestEntry, which is the classic 10-line LRU cache hook. Java 21 makes this explicit through SequencedMap: firstEntry, pollFirstEntry, putFirst, putLast and reversed. The trade-off versus HashMap is two extra references per entry, usually about 8 more bytes with compressed references, plus a little pointer maintenance.
Interview radar
Asked very often: How do you build an LRU cache in Java?
Senior rounds probe: Why get is a structural modification in access-order mode, why Collections.synchronizedMap must guard reads too, and why LRU is not the same as expiry.
Production angle: A home-grown cache with no size-by-weight, no expiry, no stampede protection and no metrics quietly becomes a memory incident.
01Picture it first: one entry, two neighborhoods
A LinkedHashMap entry lives in two neighborhoods at once. It sits in a hash bucket so get(key) can find it by hash. The same entry is also part of a doubly linked list so iteration can start at head and follow after until tail.
The picture below is the one I draw in interviews. Buckets find nodes; before/after orders nodes.
A LinkedHashMap has a HashMap bucket table for lookup and a separate before-after linked list from head through A, C and B to tail using the same entry objects
Notice that there is no second copy of the key or value in the linked list. The entry object is shared by both structures. That is why moving an entry to the tail is pointer work, not a rehash.
02LinkedHashMap under the hood
HashMap.Node has the fields needed for hashing: cached hash, key, value and the next pointer inside a bucket. LinkedHashMap.Entry adds before and after pointers. The map itself keeps head and tail references.
Try it yourself first
Before reading on, predict the iteration order after this code. Which line changes the order?
Map<String, Integer> scores = new LinkedHashMap<>(16, 0.75f, true);
scores.put("Asha", 10);
scores.put("Ravi", 20);
scores.put("Mina", 30);
scores.get("Asha");
scores.put("Ravi", 25);
System.out.println(scores.keySet());
It prints [Mina, Asha, Ravi]. The get("Asha") moves Asha to the tail. Updating Ravi's existing mapping also counts as an access in access-order mode, so Ravi moves behind Asha.
Conceptually, the important classes look like this:
class HashMapNode<K, V> {
final int hash;
final K key;
V value;
HashMapNode<K, V> next; // bucket chain
}
class LinkedHashMapEntry<K, V> extends HashMapNode<K, V> {
LinkedHashMapEntry<K, V> before;
LinkedHashMapEntry<K, V> after;
}
Notice three things:
nextis for collision handling inside one hash bucket.beforeandafterare for encounter order across the whole map.- Replacing the value for an existing key does not create a new node. In access-order mode, it can still move the existing node.
- Iteration cost is O(n) in the number of entries, regardless of table capacity, because it walks the linked list rather than scanning empty buckets.
The hook names are worth saying out loud. afterNodeAccess runs after an access and moves the node to the tail when accessOrder is true. afterNodeInsertion runs after adding a new node and gives removeEldestEntry a chance to evict the head. afterNodeRemoval unlinks the removed node from the order list.
03Insertion order, access order and Java 21 SequencedMap
The default constructor gives insertion order. Re-inserting an existing key updates its value but does not change its position. That makes LinkedHashMap useful for predictable output, such as serializing fields in first-seen order.
Access order is different. Pass true as the third constructor argument and the map tracks recency. The least recently used entry is at the head, and the most recently used entry is at the tail.
LinkedHashMap<Integer, String> pages = new LinkedHashMap<>(16, 0.75f, true);
pages.put(1, "home");
pages.put(2, "pricing");
pages.put(3, "checkout");
pages.get(1); // order: 2, 3, 1
pages.getOrDefault(2, ""); // order: 3, 1, 2
In Java 21, LinkedHashMap implements SequencedMap. That gives names to operations people used to fake with iterators.
| Method | What it means for LinkedHashMap |
|---|---|
firstEntry() |
entry at the head, usually eldest or least recently used |
lastEntry() |
entry at the tail, usually newest or most recently used |
pollFirstEntry() |
remove and return the head entry |
pollLastEntry() |
remove and return the tail entry |
putFirst(k, v) |
put the mapping and position it at the head |
putLast(k, v) |
put the mapping and position it at the tail |
reversed() |
a live reversed-order view |
Here's the catch: explicit positioning methods such as putFirst and putLast are not the same as normal access-order reads. They place the entry where you ask. Use them when order itself is your API, not when you are trying to let ordinary cache reads drive recency.
Premium lesson
Keep reading with the Java Pass
The rest of this lesson is part of Java One-Stop Interview Prep: in-depth explanations, diagrams, model answers and how to say them in the interview.
- The compact LRU cache with LinkedHashMap
- The interview LRU: HashMap plus doubly linked list
- Thread-safety, eviction policy and memory overhead
- How to say it in the interview
From ₹199 · one-time payment · No auto-renewal