What is the difference between HashMap, LinkedHashMap and TreeMap
While LinkedHashMap incurs a minor overhead during put() to manage linked pointers, iterating through a LinkedHashMap is often significantly faster than iterating through a Hash...
🟢 Junior Level
1. Comparative Overview
HashMap, LinkedHashMap, and TreeMap are the three primary implementations of the java.util.Map interface in Java. Their distinctions lie in their underlying data structures, iteration ordering, time complexity, and memory overhead:
| Characteristic | HashMap |
LinkedHashMap |
TreeMap |
|---|---|---|---|
| Encounter Order | Non-deterministic / Chaotic | Insertion-Order (or Access-Order) | Sorted Order (Natural or Comparator) |
| Underlying Data Structure | Hash Table (Buckets + Treeification) | Hash Table + Global Doubly-Linked List | Self-Balancing Red-Black Search Tree |
| Lookup / Put Complexity | $O(1)$ average | $O(1)$ average | $O(\log N)$ guaranteed |
| Iteration Complexity | $O(\text{capacity} + N)$ | $O(N)$ | $O(N)$ |
null Key Support |
✅ Allowed (maximum 1 null key) |
✅ Allowed (maximum 1 null key) |
❌ Prohibited (NullPointerException) |
| Interfaces Implemented | Map |
Map, SequencedMap (Java 21+) |
Map, NavigableMap, SequencedMap |
| Primary Use Case | Default general-purpose map | Predictable traversal, LRU cache | Range searches, sorted key output |
2. Ordering Demonstration
// 1. HashMap: Order is non-deterministic and can shift across JVMs and resizes
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put("Zulu", 1); hashMap.put("Alpha", 2); hashMap.put("Mike", 3);
System.out.println(hashMap.keySet()); // e.g. [Alpha, Zulu, Mike] (dictated by bucket indices)
// 2. LinkedHashMap: Strictly preserves insertion sequence
Map<String, Integer> linkedMap = new LinkedHashMap<>();
linkedMap.put("Zulu", 1); linkedMap.put("Alpha", 2); linkedMap.put("Mike", 3);
System.out.println(linkedMap.keySet()); // GUARANTEED: [Zulu, Alpha, Mike]
// 3. TreeMap: Naturally sorted lexicographically
Map<String, Integer> treeMap = new TreeMap<>();
treeMap.put("Zulu", 1); treeMap.put("Alpha", 2); treeMap.put("Mike", 3);
System.out.println(treeMap.keySet()); // GUARANTEED: [Alpha, Mike, Zulu]
🟡 Middle Level
1. Underlying Data Structure Mechanics
HashMap:
table[] ──► [0] ──► Node(k,v) ──► Node(k,v) (Singly-linked list or TreeNode)
[1] ──► null
[2] ──► Node(k,v)
LinkedHashMap:
table[] ──► [0] ──► Entry(k,v) ──► Entry(k,v) (Hash table for O(1) key indexing)
│ ▲
▼ │ before / after pointers
head ─────────────► [Entry1] ◄────► [Entry2] ◄────► [Entry3] ◄─── tail (Doubly-linked list)
TreeMap:
[ Root: Entry(k,v) ]
/ \
[ Left: Entry ] [ Right: Entry ] (Red-Black Tree, O(log N))
HashMaproutes keys via(capacity - 1) & hash(key). Collisions are chained into singly-linked lists that convert to balanced Red-Black Trees (TreeNode) when bucket count reaches $\ge 8$ and capacity $\ge 64$.LinkedHashMapextendsHashMap, but overrides node creation to instantiateLinkedHashMap.Entry<K,V>, which addsbeforeandafterpointers. Every insertion links the node into a global doubly-linked list spanning the entire map.TreeMapbypasses hashing completely. Nodes maintainleft,right, andparentreferences, self-balancing via rotations and recoloring to satisfy Red-Black invariants.
2. The Iteration Performance Paradox: HashMap vs. LinkedHashMap
While LinkedHashMap incurs a minor overhead during put() to manage linked pointers, iterating through a LinkedHashMap is often significantly faster than iterating through a HashMap:
- In
HashMap, the iterator must linearly inspect every slot in thetable[]array of sizecapacity. In a sparse table (e.g., pre-allocatedcapacity = 1_000_000holding only 50 entries),HashMapiterates over all 1,000,000 slots ($O(\text{capacity} + N)$), causing severe CPU cache misses. - In
LinkedHashMap, the iterator walks directly along theafterpointers fromheadtotail. It executes exactly $N$ iterations ($O(N)$), completely ignoring empty hash buckets.
3. Access-Order Mode and In-Memory LRU Caching
LinkedHashMap provides a constructor with an accessOrder configuration:
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
accessOrder = false(default): Entries are traversed in insertion order.accessOrder = true: Entries are traversed in access order (least-recently-accessed to most-recently-accessed). Invokingget(),getOrDefault(), orput()moves the referenced node to the tail of the doubly-linked list.
This enables building a lightweight Least Recently Used (LRU) Cache by overriding removeEldestEntry:
public class SimpleLruCache<K, V> extends LinkedHashMap<K, V> {
private final int maxCapacity;
public SimpleLruCache(int maxCapacity) {
super(maxCapacity, 0.75f, true); // accessOrder = true
this.maxCapacity = maxCapacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > maxCapacity; // Evicts the head (oldest accessed entry) when full
}
}
🔴 Senior Level
1. Memory Footprint Breakdown (Compressed OOPs)
On a 64-bit JVM with Compressed OOPs (-XX:+UseCompressedOops):
HashMap.Node<K,V>:- Object Header (12 bytes) +
int hash(4 bytes) + 3 references (key,value,next@ 4 bytes each = 12 bytes) + alignment padding (4 bytes) = 32 bytes.
- Object Header (12 bytes) +
LinkedHashMap.Entry<K,V>:- Inherits
HashMap.Node(32 bytes) + 2 references (before,after@ 4 bytes each = 8 bytes) = 40 bytes.
- Inherits
TreeMap.Entry<K,V>:- Object Header (12 bytes) + 5 references (
key,value,left,right,parent@ 4 bytes each = 20 bytes) +boolean color(1 byte) + alignment padding (7 bytes) = 40 bytes.
- Object Header (12 bytes) + 5 references (
| Metric | HashMap |
LinkedHashMap |
TreeMap |
|---|---|---|---|
| Node Size (Compressed OOPs) | 32 bytes | 40 bytes | 40 bytes |
Bucket Array (table[]) |
Yes (4 bytes/slot) | Yes (4 bytes/slot) | None (Root reference only) |
| CPU Cache Locality | Moderate | Low (Pointer chasing) | Very Low (Heap-scattered tree) |
| Approximate Memory per $10^6$ Entries | $\approx 36\text{ MB}$ | $\approx 44\text{ MB}$ | $\approx 40\text{ MB}$ |
2. Java 21 SequencedMap Integration (JEP 431)
Java 21 introduced SequencedMap to unify ordered mapping abstractions:
SequencedMap<String, Integer> seqMap = new LinkedHashMap<>();
seqMap.putFirst("First", 1);
seqMap.putLast("Last", 99);
// Zero-copy live reverse view in O(1):
SequencedMap<String, Integer> reversed = seqMap.reversed();
Critical Divergence Between LinkedHashMap and TreeMap:
- In
LinkedHashMap,putFirst()andputLast()are fully supported, repositioning or inserting entries at the head or tail of the doubly-linked list. - In
TreeMap,putFirst()andputLast()always throwUnsupportedOperationException, because entry position in a binary search tree is strictly determined by key comparison and cannot be dictated manually.
3. Key Equality Contract Disparity
HashMapandLinkedHashMapevaluate key equality strictly through:k1.hashCode() == k2.hashCode() && (k1 == k2 || (k1 != null && k1.equals(k2)))TreeMapnever invokeshashCode()orequals(). It relies exclusively on:comparator.compare(k1, k2) == 0or((Comparable) k1).compareTo(k2) == 0
If a class’s compareTo() is not consistent with equals() (such as BigDecimal("1.0") and BigDecimal("1.00")):
HashMapstores both entries (scale differs,equals()returnsfalse).TreeMapconsiders them duplicates (compareTo() == 0) and overwrites the value of the first with the second!
4 Tricky Questions
1. Why can invoking get(key) on a LinkedHashMap with accessOrder = true cause an infinite CPU loop or ConcurrentModificationException under concurrency?
Answer:
When accessOrder = true, get() is not a read-only operation; it is a structural modification. The invoked method unlinks the accessed Entry from its current position in the doubly-linked list and reattaches it to the tail. This mutation:
- Increments
modCount, causing concurrent active iterators to fail-fast withConcurrentModificationException. - Without synchronization, concurrent pointer updates (
beforeandafterreferences) can produce circular pointer loops. A subsequent traversal will enter an infinite loop, consuming 100% of a CPU core.
2. In what scenario does iterating over a LinkedHashMap dramatically outperform iterating over a HashMap with the same size?
Answer:
In a sparse map scenario, where initial capacity is significantly larger than the number of stored elements (e.g., new HashMap<>(1_000_000) populated with only 1,000 entries).
The HashMap iterator must scan all 1,000,000 slots of the table[] array looking for non-null bucket heads ($O(\text{capacity} + N)$), causing massive L1/L2 CPU cache misses.
LinkedHashMap iterates strictly over the continuous doubly-linked list using after references, taking exactly 1,000 steps ($O(N)$) and completely ignoring the empty bucket array.
3. Why does TreeMap reject null keys with NullPointerException, while HashMap permits one null key?
Answer:
HashMap includes special handling for null: when key == null, it forces the hash to 0 and stores the entry deterministically in table[0].
In TreeMap, placing an entry requires navigating the Red-Black Tree by comparing the candidate key against existing node keys via comparator.compare(k1, k2) or k1.compareTo(k2). Invoking compareTo() on a null reference or passing null to a standard comparator triggers a NullPointerException. (The only exception is if TreeMap is provided an explicit null-handling comparator like Comparator.nullsFirst(...)).
4. What happens when calling putFirst() or putLast() on a TreeMap in Java 21?
Answer:
Both methods throw UnsupportedOperationException.
Although TreeMap implements SequencedMap (which declares putFirst and putLast), a sorted map cannot permit caller-defined positional placement. The position of every node in a Red-Black Tree is strictly defined by the ordering invariants of its keys. Allowing putFirst("Z", val) when keys "A" through "Y" exist would corrupt the binary search tree invariants.
🎯 Interview Cheat Sheet
30-Second Elevator Pitch
“
HashMapis the standard hash-table map providing average $O(1)$ lookup and insertion with non-deterministic iteration order.LinkedHashMapextendsHashMapby maintaining a doubly-linked list across all entries, providing deterministic insertion-order or access-order (for LRU caching) at the cost of 8 additional bytes per entry.TreeMapis backed by a self-balancing Red-Black Tree, offering guaranteed $O(\log N)$ performance and sorted navigation (NavigableMap), but rejectsnullkeys and determines key equality strictly viacompareTo() == 0, ignoringequals(). In Java 21,LinkedHashMapandTreeMapimplementSequencedMap.”
High-Impact Architectural Facts
- Memory Footprint:
HashMap.Nodeis 32 bytes;LinkedHashMap.EntryandTreeMap.Entryare both 40 bytes under Compressed OOPs. - Iteration Bound:
HashMapiteration is $O(\text{capacity} + N)$;LinkedHashMapandTreeMapiteration is strictly $O(N)$. - LRU Pitfall:
LinkedHashMapwithaccessOrder = trueturnsget()into a structural mutation; concurrent unsynchronized reads corrupt pointers and throwConcurrentModificationException. - Equality Law:
HashMapandLinkedHashMaprequire bothhashCode()andequals();TreeMaprelies exclusively onComparable.compareTo()orComparator.compare(). - Java 21 JEP 431:
LinkedHashMapsupportsputFirst()/putLast();TreeMapthrowsUnsupportedOperationException.
Common Pitfalls & Red Flags
- ❌ Assuming
TreeMapis faster thanHashMap:TreeMapoperates in $O(\log N)$ and incurs frequent CPU cache misses;HashMapoperates in average $O(1)$ and is significantly faster. - ❌ Using
LinkedHashMapto sort entries by key:LinkedHashMaponly maintains insertion or access order; sorting by key requiresTreeMap. - ❌ Ignoring
compareTo()consistency withequals()inTreeMap: IfcompareTo()returns 0 for logically distinct objects,TreeMapsilently overwrites or ignores entries. - ❌ Assuming
LinkedHashMap(cap, factor, true)is thread-safe for reads: Reads modify the internal linked list; multi-threaded caching requires synchronization orCaffeine.
Related Topics
- What is Map and what implementations exist — Map hierarchy and specialized maps
- When to use TreeMap — Range navigation and practical scenarios
- What is the difference between HashSet, LinkedHashSet and TreeSet — Set counterparts
- How does HashSet work internally — Hash table internals
- What is ConcurrentHashMap — Concurrent map alternative