Skip to content
Java collections 7 min read

LinkedHashMap

LinkedHashMap is a HashMap that remembers the order in which you added entries. Whenever you need a key-value store with predictable iteration order, LinkedHashMap is your go-to choice — with almost no performance penalty over a plain HashMap.

What is LinkedHashMap?

LinkedHashMap<K, V> lives in java.util and extends HashMap<K, V>. It implements the Map interface, so every entry is a key → value pair, keys are unique, and one null key is allowed.

The critical difference from HashMap is that LinkedHashMap maintains a doubly-linked list running through all its entries. This list defines the iteration order, which can be either:

  • Insertion order (default) — entries come out in the order you called put()
  • Access order — entries are reordered so the most-recently-accessed entry is last (useful for LRU caches — more on this below)
import java.util.LinkedHashMap;

public class BasicLinkedHashMap {
    public static void main(String[] args) {
        LinkedHashMap<String, Integer> scores = new LinkedHashMap<>();
        scores.put("Alice", 95);
        scores.put("Bob",   87);
        scores.put("Carol", 92);

        System.out.println(scores);
        System.out.println(scores.get("Bob"));
    }
}

Output:

{Alice=95, Bob=87, Carol=92}
87

Notice how the output keeps the exact insertion order — unlike a plain HashMap, which gives no such guarantee.

Creating a LinkedHashMap

Default Constructor (insertion order)

LinkedHashMap<String, String> capitals = new LinkedHashMap<>();
capitals.put("France", "Paris");
capitals.put("Japan",  "Tokyo");
capitals.put("Brazil", "Brasília");

for (var entry : capitals.entrySet()) {
    System.out.println(entry.getKey() + " → " + entry.getValue());
}

Output:

France → Paris
Japan → Tokyo
Brazil → Brasília

With Initial Capacity and Load Factor

// capacity 16, load factor 0.75 (same defaults as HashMap)
LinkedHashMap<Integer, String> map = new LinkedHashMap<>(16, 0.75f);

Access-Order Mode

Pass true as the third constructor argument to enable access order:

LinkedHashMap<String, Integer> accessMap =
        new LinkedHashMap<>(16, 0.75f, true); // accessOrder = true

accessMap.put("a", 1);
accessMap.put("b", 2);
accessMap.put("c", 3);

accessMap.get("a"); // accessing "a" moves it to the tail
System.out.println(accessMap);

Output:

{b=2, c=3, a=1}

"a" was accessed last, so it appears at the end. This ordering is the foundation for building an LRU (Least-Recently-Used) cache.

Common Methods

LinkedHashMap inherits all HashMap methods — there is no new API. The methods you will use most:

MethodDescription
put(K key, V value)Inserts or updates an entry
get(Object key)Returns the value for the key, or null
getOrDefault(key, def)Returns value or a default if absent
containsKey(Object key)true if the key exists
containsValue(Object value)true if the value exists
remove(Object key)Removes the entry for that key
size()Number of entries
isEmpty()true when empty
clear()Removes all entries
keySet()Set of keys (insertion/access ordered)
values()Collection of values (ordered)
entrySet()Set of Map.Entry objects (ordered)
putIfAbsent(K, V)Inserts only when key is absent
computeIfAbsent(K, fn)Inserts computed value if key is absent
import java.util.LinkedHashMap;

public class LinkedHashMapMethods {
    public static void main(String[] args) {
        LinkedHashMap<String, Integer> stock = new LinkedHashMap<>();
        stock.put("apples",  50);
        stock.put("bananas", 30);
        stock.put("cherries", 10);

        // getOrDefault
        System.out.println(stock.getOrDefault("dates", 0)); // 0

        // putIfAbsent
        stock.putIfAbsent("apples", 999); // ignored — key exists
        stock.putIfAbsent("dates",  25);  // inserted

        // iterate in insertion order
        stock.forEach((item, qty) ->
            System.out.println(item + ": " + qty));
    }
}

Output:

0
apples: 50
bananas: 30
cherries: 10
dates: 25

Iterating a LinkedHashMap

Because iteration order is guaranteed, LinkedHashMap is one of the easiest maps to reason about when printing or processing entries.

import java.util.LinkedHashMap;
import java.util.Map;

public class IterateLinkedHashMap {
    public static void main(String[] args) {
        LinkedHashMap<String, Double> prices = new LinkedHashMap<>();
        prices.put("coffee", 3.50);
        prices.put("tea",    2.00);
        prices.put("juice",  4.25);

        // 1. entrySet (most common)
        for (Map.Entry<String, Double> e : prices.entrySet()) {
            System.out.printf("%-10s $%.2f%n", e.getKey(), e.getValue());
        }

        // 2. forEach (Java 8+)
        System.out.println("---");
        prices.forEach((k, v) -> System.out.println(k + " = " + v));

        // 3. keySet
        System.out.println("---");
        for (String key : prices.keySet()) {
            System.out.println(key);
        }
    }
}

Output:

coffee     $3.50
tea        $2.00
juice      $4.25
---
coffee = 3.50
tea = 2.00
juice = 4.25
---
coffee
tea
juice

Building an LRU Cache

The most powerful real-world use of LinkedHashMap is a self-maintaining Least-Recently-Used cache. Override removeEldestEntry() to evict the oldest entry once the cache exceeds your chosen capacity.

import java.util.LinkedHashMap;
import java.util.Map;

public class LRUCache<K, V> extends LinkedHashMap<K, V> {

    private final int capacity;

    public LRUCache(int capacity) {
        super(capacity, 0.75f, true); // accessOrder = true
        this.capacity = capacity;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;
    }

    public static void main(String[] args) {
        LRUCache<Integer, String> cache = new LRUCache<>(3);
        cache.put(1, "one");
        cache.put(2, "two");
        cache.put(3, "three");

        cache.get(1);          // access "one" → moves to tail
        cache.put(4, "four");  // size exceeds 3 → evicts eldest ("two")

        System.out.println(cache); // {three=three, one=one, four=four}
    }
}

Output:

{3=three, 1=one, 4=four}

Tip: For production-grade caches, consider Guava’s CacheBuilder or Caffeine, which offer TTL, statistics, and thread safety. The LinkedHashMap approach is single-threaded only.

Under the Hood

Understanding how LinkedHashMap works internally helps you make better decisions about when to use it and how to tune it.

Data Structure

LinkedHashMap reuses the hash table (array of buckets + chaining/tree) from HashMap — every entry lookup is still O(1) average. On top of that, each Entry node carries two additional pointers: before and after. These form a doubly-linked list that is maintained separately from the bucket structure.

Hash table (buckets):
  [0] → Entry("Bob", 87)
  [2] → Entry("Alice", 95)
  [5] → Entry("Carol", 92)

Doubly-linked list (insertion order):
  head ↔ Entry("Alice") ↔ Entry("Bob") ↔ Entry("Carol") ↔ tail

When you call put(), a new node is appended to the tail of the linked list. When you call remove(), the node is unlinked from both the bucket chain and the doubly-linked list in O(1).

Memory Overhead

Each entry carries two extra object references (before / after) compared to a plain HashMap entry — roughly 16 extra bytes per entry on a 64-bit JVM. For most applications this is negligible, but it matters when you store millions of tiny entries.

Access Order Mechanics

In access-order mode, every call to get(), put() (on an existing key), or getOrDefault() calls the internal afterNodeAccess() method, which unlinks the accessed node and reattaches it at the tail of the doubly-linked list. This is an O(1) operation, so access-order mode adds almost no overhead per lookup.

Thread Safety

LinkedHashMap is not thread-safe. If multiple threads access the same instance concurrently and at least one modifies it, you must synchronize externally:

Map<String, Integer> syncMap =
    Collections.synchronizedMap(new LinkedHashMap<>());

For highly concurrent workloads, look at ConcurrentHashMap instead (though it does not preserve insertion order).

LinkedHashMap vs HashMap vs TreeMap

FeatureHashMapLinkedHashMapTreeMap
OrderingNone (random)Insertion or access orderNatural / Comparator sort
Null keys1 allowed1 allowedNot allowed
Performance (get/put)O(1) avgO(1) avgO(log n)
Memory overheadLowestSlightly higherHigher
Thread-safeNoNoNo
Best forFast lookups, no order neededPredictable order, LRU cacheSorted iteration, range queries

Note: If you just need to iterate a map in the order it was populated — for example, to render a form or build a JSON response with stable field order — LinkedHashMap is the cleanest solution.

Practical Example: Word Frequency Counter (Ordered)

import java.util.LinkedHashMap;

public class WordFrequency {
    public static void main(String[] args) {
        String sentence = "the quick brown fox jumps over the lazy fox";
        LinkedHashMap<String, Integer> freq = new LinkedHashMap<>();

        for (String word : sentence.split(" ")) {
            freq.merge(word, 1, Integer::sum);
        }

        freq.forEach((word, count) ->
            System.out.printf("%-10s %d%n", word, count));
    }
}

Output:

the        2
quick      1
brown      1
fox        2
jumps      1
over       1
lazy       1

The words appear in the order they were first encountered, while their counts accumulate correctly. The merge() method (Java 8+) handles both insertion and update in one call.

  • HashMap — the parent class; understand its hash table internals before diving into LinkedHashMap
  • Working of HashMap (Internals) — deep-dive into hashing, bucket collisions, and tree-ification in Java 8+
  • TreeMap — when you need entries sorted by key rather than insertion order
  • LinkedHashSet — the Set counterpart that preserves insertion order without duplicates
  • Map Interface — the common contract that LinkedHashMap, HashMap, and TreeMap all implement
  • Concurrent Collections — thread-safe map alternatives when multiple threads share state
Last updated June 13, 2026
Was this helpful?