LinkedHashMap in Java: Access Order & Building an LRU Cache
-
Last Updated: August 7, 2026
-
By: javahandson
-
Series
Learn Java in a easy way
Learn LinkedHashMap in Java with simple examples. See how insertion order and access order work, then build a working LRU cache in just a few lines.
So you’ve been using HashMap for a while. It stores your keys and values, and it does the job. But then you print it, and the keys come out in some random order. This is the exact gap that LinkedHashMap in Java fills. That random order is a bit annoying, right?
You add “one”, then “two”, then “three”. You print the map, and out comes “two”, “one”, “three”. No pattern. Just wherever the hash function decided to drop them.
This is where LinkedHashMap in Java comes to the rescue. It works just like HashMap, but it also remembers the order you put things in. Print it, and the keys come out in that same order. Neat and predictable.
But there’s a hidden second gear here too. LinkedHashMap can switch to a special access order mode. With one small trick, that mode lets you build an LRU cache in just a few lines. We’ll get there step by step.
Here’s what we’ll walk through together:
No deep theory needed. If you’ve used a HashMap even once, you’re ready to roll.
A LinkedHashMap is a Map that keeps its entries in a known order. Most of the time, that order is simply the order you added them. Add “apple” first, and “apple” shows up first.
Think of a guest book at a wedding. People sign it one after another. When you read it later, you see names in the exact order they walked in. A LinkedHashMap behaves the same way.
Here’s the key fact. LinkedHashMap is actually a subclass of HashMap. So it borrows all the fast lookup power of HashMap for free.
On top of that base, it adds one extra thing: a doubly linked list running through all the entries. That list is what remembers the order. HashMap alone has no such list, which is why its order looks random.
It helps to see them next to each other. Both store key-value pairs. Both give you fast get and put. The difference is all about order and a little extra memory.
| Feature | HashMap | LinkedHashMap |
|---|---|---|
| Order of keys | No fixed order | Insertion order (or access order) |
| Backed by | Array of buckets | Buckets plus a linked list |
| Extra memory | Lower | Slightly higher |
| Lookup speed | O(1) average | O(1) average |
| Special access mode | No | Yes, for LRU-style use |
| Allows one null key | Yes | Yes |
So LinkedHashMap trades a bit of extra memory for predictable ordering. For most code that needs a steady output order, that trade is well worth it.
Let’s put insertion order on display. Watch how the print output matches the order of the puts.
Map<String, Integer> scores = new LinkedHashMap<>();
scores.put("Ravi", 90);
scores.put("Meena", 85);
scores.put("Arjun", 95);
System.out.println(scores);
// Output: {Ravi=90, Meena=85, Arjun=95}The keys come out in the same order you put them in. Try the same code with a plain HashMap, and the order may jump around. That single line of difference is the whole point.
Notice the left side says Map, not LinkedHashMap. That’s a good habit. You code to the interface, so you can swap the map type later with little fuss.
Because it extends HashMap, the method list feels familiar. You already know most of these from HashMap.
Nothing new to memorize here. The magic is not in new methods. It’s in the ordering those methods now respect.
The names look alike, so beginners mix these two up. But they solve very different problems, and it’s worth clearing up early.
A LinkedList is a list of single values, one after another. A LinkedHashMap is a map of key-value pairs. The word “linked” here only means it uses an internal list to remember order.
So don’t reach for a LinkedHashMap when you just need a list. And don’t reach for a LinkedList when you need key-based lookups. Different tools for different jobs.
Beginners often ask about null keys and null values. The good news is that LinkedHashMap follows the same rules as HashMap here.
You can store one null key. You can also store as many null values as you like. Nothing special changes because of the ordering list.
Map<String, String> map = new LinkedHashMap<>();
map.put(null, "no key");
map.put("x", null);
map.put("y", null);
System.out.println(map);
// Output: {null=no key, x=null, y=null}The null key just takes its place in the order like any other key. So if you know HashMap’s null behavior, you already know this too.
Here’s a small but real benefit. When you loop over a LinkedHashMap, the order never surprises you. It’s the same every single run.
This matters more than it sounds. Tests that check output become stable. Logs read in a sensible order. Debugging gets easier because you’re not chasing a shuffled map.
Map<String, Integer> map = new LinkedHashMap<>();
map.put("one", 1);
map.put("two", 2);
map.put("three", 3);
for (Map.Entry<String, Integer> e : map.entrySet()) {
System.out.println(e.getKey() + " = " + e.getValue());
}
// one = 1
// two = 2
// three = 3 (always this order)Run that loop a hundred times, and you get the same three lines in the same order. A plain HashMap gives no such promise, and that’s the whole reason this class exists.
Let’s peek inside for a moment. You don’t need this to use the class, but it makes the behavior click.
A HashMap stores entries in buckets. The bucket for a key comes from its hash code. That’s why the order looks scattered, because it follows the hash, not your intent.
LinkedHashMap keeps those same buckets. But it also threads a doubly linked list through every entry. Each entry knows the one before it and the one after it.
When you add a new entry, it joins the end of that list. So the list always reflects the order of your puts. Reading keySet() just walks this list from head to tail.
Say you update an existing key with put. The value changes, but the entry keeps its original spot in the list. An update is not a new insertion, so the order holds.
Remove a key, and that entry unlinks itself from the list. The neighbors join hands and close the gap. The rest of the order stays exactly as it was.
Map<String, Integer> map = new LinkedHashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
map.put("a", 10); // update, not a new insert
System.out.println(map);
// Output: {a=10, b=2, c=3} -> a stays first| Interview Insight Q: Does updating a key move it to the end in a LinkedHashMap? A: In the default insertion-order mode, no. An update changes the value but keeps the entry in its original position. The entry only moves if you turn on access order mode, which we cover next. |
That linked list isn’t free. Each entry now carries two extra references: one pointing to the entry before it, one to the entry after. So every entry uses a bit more memory than in a plain HashMap.
For small maps, you’ll never notice. For maps with millions of entries, that overhead adds up. So keep the trade-off in mind when memory is tight.
So the cost is memory, not speed. In fact, looping over a LinkedHashMap can beat a HashMap, because it follows the list instead of scanning empty buckets.
Like HashMap, a LinkedHashMap is not thread-safe on its own. If several threads write to it at once, things can break in strange ways.
When you need safe access from many threads, wrap it. Collections.synchronizedMap gives you a guarded version. For heavy concurrent work, though, other classes suit the job better.
Map<String, Integer> safe =
Collections.synchronizedMap(new LinkedHashMap<>());Just remember that even the wrapped version needs care during iteration. You still have to synchronize on the map while you loop over it.
Inside the library, LinkedHashMap defines its own entry type. It takes a normal HashMap entry and adds two fields, named before and after.
Those two fields are the linked list. The before field points back, the after field points forward. Together they chain every entry in one long line.
You never touch these fields yourself. But knowing they exist explains why order is cheap to keep. Moving an entry is just a few pointer swaps, not a full copy.
Now for the fun part. LinkedHashMap has a mode where the order is not about insertion at all. Instead, it follows how recently you touched each entry.
There’s a special constructor with three arguments. The third one is a boolean flag. Pass true, and you switch from insertion order to access order.
// initialCapacity, loadFactor, accessOrder
Map<String, Integer> map =
new LinkedHashMap<>(16, 0.75f, true);With that flag on, the map watches your get and put calls. Every time you access an entry, it quietly slides that entry to the end of the list. The end means “most recently used”.
Let’s see the reordering live. We add three keys, then read one of them. That read pushes it to the back.
Map<String, Integer> map =
new LinkedHashMap<>(16, 0.75f, true);
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
System.out.println(map); // {a=1, b=2, c=3}
map.get("a"); // touch "a"
System.out.println(map); // {b=2, c=3, a=1}See how “a” jumped to the end after we read it? The least recently touched key now sits at the front. That front spot is the oldest, the one you haven’t used in a while.
This tiny behavior is the seed of an LRU cache. Hold that thought, because we’re about to build one.
Two modes, two very different behaviors. It helps to see them lined up before we move on.
| Behavior | Insertion order (default) | Access order (flag = true) |
|---|---|---|
| Set by | Order of put calls | How recently touched |
| get moves entry? | No | Yes, to the end |
| put update moves entry? | No | Yes, to the end |
| Front of list is | Oldest inserted | Least recently used |
| Main use | Predictable output | LRU cache |
Default mode is about when you added a key. Access order is about when you last used it. That one word, “used”, makes all the difference for caching.
LRU stands for Least Recently Used. It’s a cache that keeps a fixed number of items. When it gets full, it kicks out the item you haven’t touched in the longest time.
Picture a small desk with room for six books. You keep pulling books to read. When a seventh book arrives, the desk is full.
So you remove the book you’ve ignored the longest and put the new one down. The books you read often stay close. The forgotten ones get pushed out. That’s LRU in a nutshell.
Memory is limited, and you can’t keep everything. A cache needs a rule to decide what to drop. LRU is a solid default because recent use often predicts future use.
You’ll find LRU caches everywhere. Web browsers, databases, and operating systems all lean on this idea to keep hot data fast and close.
Here’s the payoff. Because access order already moves touched entries to the end, the front of the list is always the least recently used. We just need to remove that front entry when the map grows too big.
LinkedHashMap gives us a method built for exactly this. It’s called removeEldestEntry. After every put, the map calls this method and asks a simple question.
The question is: should I drop the oldest entry now? Return true, and it evicts the eldest. Return false, and it keeps everything. By default, it always returns false.
So we override it. We say “return true once the size passes my limit”. That one line turns a LinkedHashMap into a working LRU cache.
Let’s write it out. It’s shorter than you’d expect. We extend LinkedHashMap, turn on access order, and override one method.
class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int capacity;
LRUCache(int capacity) {
// access order = true is the key part
super(16, 0.75f, true);
this.capacity = capacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
// evict once we go past capacity
return size() > capacity;
}
}That’s the whole cache. No manual list handling, no timers, no counters. LinkedHashMap does the heavy lifting, and we just set the rule.
Now let’s give it a spin with a capacity of three. Watch what happens when we add a fourth item.
LRUCache<String, Integer> cache = new LRUCache<>(3);
cache.put("a", 1);
cache.put("b", 2);
cache.put("c", 3);
System.out.println(cache); // {a=1, b=2, c=3}
cache.get("a"); // touch "a", now newest
cache.put("d", 4); // over capacity, evict eldest
System.out.println(cache); // {c=3, a=1, d=4}Let’s read that output carefully. We touched “a”, so it moved to the back and became safe. When “d” arrived, the cache was full, so it evicted “b”, the oldest untouched key.
The result is exactly what LRU promises. Hot keys stay. Cold keys leave. And we wrote almost no logic to make it happen.
| Interview Insight Q: Why do we call super(16, 0.75f, true) in the LRU cache? A: The third argument, true, turns on access order. Without it, the map stays in insertion order, and get calls won’t move entries. Then the eldest entry would just be the oldest inserted, not the least recently used, which breaks the LRU behavior. |
It’s worth being precise about the timing here. The map checks for eviction right after each put, not during a get.
So the flow is simple. You put a new key. The map adds it. Then it calls removeEldestEntry and asks whether to trim. If you return true, it removes the front entry in one clean step.
This means the cache never grows past your limit by more than a moment. It swells to size plus one, evicts, and settles back down. You get a steady, bounded size for free.
Let’s slow the last example right down. Watching each step makes the eviction crystal clear.
Read those steps once more, and the whole cache clicks. The class handles the list moves. Our one method just says when to trim.
A fair question comes up here. Which calls actually count as “using” an entry in access order mode?
Both get and put count. A get that finds a key moves it to the back. A put that updates an existing key does the same. But containsKey does not move anything, so use get when you want the entry marked as fresh.
Map<String, Integer> map =
new LinkedHashMap<>(16, 0.75f, true);
map.put("a", 1);
map.put("b", 2);
map.containsKey("a"); // does NOT reorder
System.out.println(map); // {a=1, b=2}
map.get("a"); // DOES reorder
System.out.println(map); // {b=2, a=1}This trips people up in real caches. If you check a key with containsKey and expect it to count as a hit, it won’t. Reach for get instead, so the entry truly moves to fresh.
It’s a great tool, but not for every job. Let’s sort out when it shines and when a plain HashMap is the smarter pick.
Choose LinkedHashMap when order matters to you. If your output must follow insertion order, this is your class.
If you never care about order, skip the extra list. A plain HashMap uses a touch less memory and does the same lookups.
So for a simple lookup table where order is meaningless, HashMap wins on simplicity. Reach for LinkedHashMap only when order earns its keep.
Beyond the LRU cache, this class shows up in plenty of small, useful spots. Here are some you’ll likely meet.
None of these are fancy. That’s the point. LinkedHashMap quietly solves the “I need a map, but order matters” problem without any extra work from you.
Sometimes you want the eldest entry without building a full cache. You can peek at the first key using an iterator over the keys.
Map<String, Integer> map = new LinkedHashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
// first key is the eldest in insertion order
String eldest = map.keySet().iterator().next();
map.remove(eldest);
System.out.println(map);
// Output: {b=2, c=3}This is handy for a quick trim without subclassing anything. For a real cache, though, the removeEldestEntry approach stays cleaner and safer.
Java gives you three main maps, and picking the right one saves headaches later. Let’s compare them so the choice feels obvious.
Each map answers the ordering question differently. That’s really the heart of it.
| Map type | Ordering | Speed | Best for |
|---|---|---|---|
| HashMap | None (random) | O(1) average | Fast lookups, order not needed |
| LinkedHashMap | Insertion or access | O(1) average | Predictable order, LRU cache |
| TreeMap | Sorted by key | O(log n) | Keys you need in sorted order |
So HashMap is fastest and forgets order. LinkedHashMap keeps the order you care about. TreeMap sorts keys but pays a small speed cost for it.
Don’t overthink it. A quick rule handles most cases.
Start with HashMap by default. Move to LinkedHashMap the moment order shows up in your requirements. Save TreeMap for true sorting needs.
A few traps snag beginners often. Let’s name them so you can step around each one.
People build an LRU cache but leave the flag off. Then get calls don’t reorder anything, and eviction picks the wrong key. Always pass true as the third constructor argument for LRU work.
The method must return a boolean, not remove anything by hand. Return true to evict, false to keep. Don’t call remove inside it, or you’ll fight the class and get odd results.
LinkedHashMap keeps insertion or access order, not sorted order. If you want keys sorted by value, that’s a different class, TreeMap. Don’t mix up ordered with sorted, they mean different things.
Change the map inside a for-each loop, and you may hit a ConcurrentModificationException. To remove safely while looping, use an Iterator and its remove method instead.
A: It is a Map that keeps its entries in a known order. By default it uses insertion order, so keys come out in the order you added them. It extends HashMap and adds a doubly linked list to track that order.
A: A HashMap has no fixed order for its keys. A LinkedHashMap keeps insertion order, or access order if you turn that mode on. The trade-off is a little extra memory for the linked list. Lookups stay O(1) on average in both.
A: It is a mode you switch on with the three-argument constructor by passing true as the last flag. In this mode, every get or put moves that entry to the end of the list. So the front of the list is always the least recently used entry.
A: Extend LinkedHashMap and call super(16, 0.75f, true) to turn on access order. Then override removeEldestEntry to return true once the size passes your capacity. The class handles the eviction for you.
A: LinkedHashMap calls this method after every put. It asks whether to drop the oldest entry. Return true to evict the eldest, or false to keep everything. By default it always returns false.
A: Yes. Both get and put move an entry to the newest end. But containsKey does not reorder anything. So use get when you want the entry marked as recently used.
A: No. A LinkedList holds single values with no keys. A LinkedHashMap holds key-value pairs like any map. The word linked only means it uses an internal list to remember order.
A: Yes, just like HashMap. You can store one null key and as many null values as you like. The null key takes its place in the order like any other key.
A: No, not on its own. For safe access from many threads, wrap it with Collections.synchronizedMap. You still need to synchronize on the map while you iterate over it.
A: Use LinkedHashMap when you need insertion order or an LRU cache, with O(1) average lookups. Use TreeMap when you need keys in sorted order, which costs O(log n) per operation. Ordered and sorted are not the same thing.
Let’s tie it all together. A LinkedHashMap is a HashMap that remembers order. By default, it keeps your insertion order, so iteration is predictable.
Flip on access order mode, and it starts tracking how recently you touch each key. That single flag, plus the removeEldestEntry hook, gives you a clean LRU cache in a handful of lines.
Reach for LinkedHashMap when order matters or when you need an LRU cache. Stick with HashMap when order is noise. Pick the right one, and your code stays both fast and clear.
Next up, we open TreeMap. You’ll see how it keeps keys in true sorted order, and where that sorting beats a linked list of insertions.