What is Map and what implementations exist
The Map
🟢 Junior Level
1. What is a Map
The Map<K, V> interface (java.util.Map), also known as an associative array or dictionary, represents a data structure that maps unique Keys to Values:
- Keys (
K): Must be strictly unique. Adding an existing key replaces the previous associated value. - Values (
V): May contain duplicates; multiple keys can point to identical values. - Each key maps to at most one corresponding value.
Map<String, Integer> salaries = new HashMap<>();
salaries.put("Alex", 120_000);
salaries.put("Maria", 150_000);
// Retrieving a value by key:
Integer alexSalary = salaries.get("Alex"); // 120000
// Checking key existence:
boolean hasMaria = salaries.containsKey("Maria"); // true
2. Why Does Map NOT Extend Collection?
A quintessential Java interview question: Map is deliberately separate from the Collection interface hierarchy due to two fundamental architectural reasons:
- Incompatible Element Semantics:
Collection<E>manages a container of discrete single items (add(E e),iterator()), whereasMap<K, V>manages 2-dimensional key-value pairs (put(K key, V value)). - Collection Views: Rather than supporting direct iteration,
Mapexposes three distinct collection views:keySet()$\to$Set<K>(a set of unique keys).values()$\to$Collection<V>(a collection of non-unique values).entrySet()$\to$Set<Map.Entry<K, V>>(a set of key-value pair entries).
3. Master Summary Table of Map Implementations
| Implementation | Key Ordering | Complexity (get/put) |
null Allowed? |
Thread-Safe? | Primary Use Case |
|---|---|---|---|---|---|
HashMap |
Non-deterministic | $O(1)$ average | Key: ✅ (1), Val: ✅ | ❌ No | General-purpose default mapping |
LinkedHashMap |
Insertion or Access Order | $O(1)$ average | Key: ✅ (1), Val: ✅ | ❌ No | Deterministic traversal, LRU cache |
TreeMap |
Sorted (Comparable/Comparator) | $O(\log N)$ | Key: ❌ (NPE), Val: ✅ | ❌ No | Sorted reporting, range queries |
ConcurrentHashMap |
Non-deterministic | $O(1)$ average | Key: ❌, Val: ❌ | ✅ Yes | Scalable high-concurrency systems |
EnumMap |
Declaration order of enum | $O(1)$ (Array index) | Key: ❌, Val: ✅ | ❌ No | Enum keys (fastest map in JVM) |
WeakHashMap |
Non-deterministic | $O(1)$ average | Key: ✅, Val: ✅ | ❌ No | GC-driven metadata associations |
IdentityHashMap |
Non-deterministic (==) |
$O(1)$ average | Key: ✅, Val: ✅ | ❌ No | Serializers, cyclic graph traversal |
🟡 Middle Level
1. Specialized High-Performance Implementations
EnumMap: Peak Mechanical Sympathy
When keys are instances of an enum, using HashMap is an architectural anti-pattern. EnumMap is backed by a compact, single-dimensional array:
enum TaskPriority { LOW, MEDIUM, HIGH, URGENT }
Map<TaskPriority, String> tasks = new EnumMap<>(TaskPriority.class);
tasks.put(TaskPriority.HIGH, "Hotfix production outage");
- Why it surpasses
HashMap: Keys are indexed directly by theirenum.ordinal(). This eliminateshashCode()computation, eliminates hash collisions, requires no bucket linked lists or trees, and achieves direct array reads (vals[key.ordinal()]) in 1 CPU clock cycle.
IdentityHashMap: Reference Identity (==)
Unlike all other standard maps, IdentityHashMap completely ignores equals() and hashCode(). Keys $k_1$ and $k_2$ match if and only if $k_1 == k_2$.
- Internally uses open addressing in a flat array
Object[] table, alternating keys and values (table[i] = key,table[i+1] = value), hashed usingSystem.identityHashCode(). - Indispensable in serialization frameworks (Jackson, Kryo) and deep-cloning utilities to track visited objects and prevent infinite recursion in cyclic graphs.
WeakHashMap: Weak Key Tracking
Keys are wrapped inside java.lang.ref.WeakReference. When a key object loses all strong references across the application, the garbage collector marks the key for collection. Upon subsequent operations on the map, stale entries are purged via an internal ReferenceQueue.
2. Functional Mutation API (Java 8+)
Java 8 modernized map manipulation with higher-order functional methods:
Map<String, List<String>> userRoles = new HashMap<>();
// computeIfAbsent: initializes and inserts the collection only if key was missing:
userRoles.computeIfAbsent("john", k -> new ArrayList<>()).add("ROLE_ADMIN");
// merge: perfect for atomic-like counters and string aggregations:
Map<String, Integer> wordFrequency = new HashMap<>();
wordFrequency.merge("engine", 1, Integer::sum); // inserts 1 if absent, else oldVal + 1
// getOrDefault: avoids null checks and NullPointerExceptions:
int count = wordFrequency.getOrDefault("missing", 0);
3. Immutable Factory Methods (Java 9+)
Map.of() and Map.ofEntries() return unmodifiable, compact map implementations:
// Up to 10 key-value pairs:
Map<String, String> config = Map.of(
"env", "production",
"timeout", "5000"
);
// More than 10 pairs:
Map<Integer, String> statusCodes = Map.ofEntries(
Map.entry(200, "OK"),
Map.entry(404, "Not Found"),
Map.entry(500, "Internal Server Error")
);
Architectural Properties:
- Strictly reject
nullkeys and values (NullPointerException). - Consume $2\times\text{ to }3\times$ less memory than
HashMapby utilizing a flat linear-probing array instead of allocating individualNodeobjects. - Randomized Iteration Order: Key encounter order is intentionally shuffled on every JVM execution to prevent applications from writing code that depends on pseudo-accidental ordering.
🔴 Senior Level
1. Java 21 Architectural Evolution: SequencedMap (JEP 431)
Prior to Java 21, Java lacked a common abstraction for maps with a well-defined encounter order. Java 21 introduced SequencedMap<K, V>:
┌─────────────────┐
│ Map<K, V> │
└────────┬────────┘
│ extends
┌────────┴────────┐
│ SequencedMap │
└───┬─────────┬───┘
│ │
implements implements
│ │
┌──────────┴──┐ ┌──┴───────────┐
│LinkedHashMap│ │ NavigableMap │
└─────────────┘ └──┬───────────┘
│ implements
┌──┴───────────┐
│ TreeMap │
└──────────────┘
SequencedMap standardizes first/last entry manipulation across ordered maps:
SequencedMap<String, Integer> map = new LinkedHashMap<>();
map.putFirst("A", 1);
map.putLast("Z", 26);
Map.Entry<String, Integer> first = map.firstEntry(); // "A" -> 1
Map.Entry<String, Integer> last = map.lastEntry(); // "Z" -> 26
// Constant-time zero-copy reverse view:
SequencedMap<String, Integer> reversed = map.reversed();
Note: HashMap does not implement SequencedMap because it provides no ordering guarantees.
2. Constructing an LRU Cache via LinkedHashMap
LinkedHashMap provides native support for building an in-memory Least Recently Used (LRU) eviction cache via its special constructor and removeEldestEntry() hook:
public class LruCache<K, V> extends LinkedHashMap<K, V> {
private final int maxCapacity;
public LruCache(int maxCapacity) {
// accessOrder = true: elements are reordered upon read (get) as well as write (put)
super(maxCapacity, 0.75f, true);
this.maxCapacity = maxCapacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
// Automatically evicts the least recently accessed entry when capacity is exceeded:
return size() > maxCapacity;
}
}
[!WARNING] Thread-Safety Warning: This pattern is not thread-safe. When
accessOrder = true, callingget(key)is a structural mutation (it unlinks the node and appends it to the tail of the doubly-linked list). Concurrent reads without synchronization will corrupt pointers, create circular loops, or throwConcurrentModificationException. For production concurrency, utilize dedicated caching engines like Caffeine.
3. Obsolete Legacy Maps: Hashtable and Properties
Hashtable: Originating in Java 1.0, every method is guarded by a method-levelsynchronizedlock on the entire instance. It rejectsnullkeys and values. Completely superseded byConcurrentHashMap.Properties: ExtendsHashtable<Object, Object>. A severe type-safety design flaw: whilesetProperty(String, String)andgetProperty(String)accept and return strings, because it inheritsHashtable.put(Object, Object), developers can insert arbitrary non-string objects, causing runtimeClassCastExceptions when callinggetProperty().
4 Tricky Questions
1. Why does Map not extend Collection, but Map.values() returns a Collection<V>?
Answer:
Collection<E> defines operations on a sequence of homogeneous single elements, mandating the add(E e) contract. Map operates on 2-dimensional associations (Map.Entry<K, V>), making single-element additions meaningless.
Map.keySet() returns a Set<K> because map keys are guaranteed to be unique. However, Map.values() cannot return a Set<V> because multiple distinct keys can map to the exact same value. Therefore, values() returns the broader Collection<V> interface, permitting duplicate value elements.
2. Why is EnumMap architecturally superior to HashMap for enum keys?
Answer:
EnumMap exploits the JVM guarantee that all enum constants have fixed, consecutive integer ordinals (ordinal()).
Under the hood:
EnumMapstores values in a flatObject[] valsarray sized to the number of enum constants.- Lookups and insertions bypass
hashCode(), avoid bitwise masking, require no bucket lists, and never suffer hash collisions. - Access is compiled down to a single bounds check and indexed array read (
vals[key.ordinal()]), executing in 1 CPU cycle with superior cache locality and zero node object allocation.
3. Why can reading from a LinkedHashMap via get() throw ConcurrentModificationException during iteration?
Answer:
If the LinkedHashMap was instantiated with accessOrder = true (access-order mode for LRU caching), invoking get(key) modifies the structural ordering of the collection by relocating the accessed entry to the tail of its internal doubly-linked list.
This structural mutation increments the internal modCount field. If an active Iterator is traversing keySet(), values(), or entrySet() while get() is invoked, the iterator’s next step detects that expectedModCount != modCount and throws a ConcurrentModificationException.
4. How does IdentityHashMap evaluate key equality, and in what mission-critical domains is it indispensable?
Answer:
IdentityHashMap uses reference equality (k1 == k2) and System.identityHashCode(k) rather than virtual .equals() and .hashCode().
It is indispensable in:
- Object Graph Traversal / Serialization: Frameworks like Jackson, Kryo, or Spring Object-Copier maintain an
IdentityHashMap<Object, Boolean> visitedto track processed heap instances. This prevents infinite recursion when traversing circular references (A -> B -> A), even ifA.equals(B)might otherwise evaluate totrue. - Dynamic Bytecode & Proxies: Associating internal metadata with runtime proxy instances without risking collisions with overridden domain
equals()methods.
🎯 Interview Cheat Sheet
30-Second Elevator Pitch
“
Map<K, V>represents a key-value dictionary that enforces key uniqueness while allowing duplicate values. It is intentionally decoupled fromCollectionbecause it operates on pairs, exposing collection views viakeySet(),values(), andentrySet(). Primary implementations includeHashMap($O(1)$ general default),LinkedHashMap(preserves insertion or access order for LRU caches),TreeMap($O(\log N)$ sorted via Red-Black Tree), andConcurrentHashMap(lock-free CAS and bucket synchronization for high-concurrency). Specialized variants likeEnumMapprovide hardware-level array access for enums, andIdentityHashMapmatches keys via==for object graph traversal. In Java 21, ordered maps implementSequencedMap.”
High-Impact Architectural Facts
- Views Backing:
keySet(),values(), andentrySet()return backed views, not copies; modifying or removing through a view iterator mutates the parent map. - EnumMap Speed: Flat array indexing via
ordinal()eliminates hashing, collisions, and node overhead. - Java 9 Immutability:
Map.of()uses flat compact arrays with linear probing, randomized iteration order, and null prohibition. - LRU Access Trap: In
LinkedHashMap(cap, factor, true),get()mutatesmodCount, meaning concurrent reads require synchronization to avoid data corruption. - SequencedMap (Java 21): Extends
MapwithfirstEntry(),lastEntry(),putFirst(),putLast(), and zero-copyreversed().
Common Pitfalls & Red Flags
- ❌ Calling
MapaCollection: An immediate red flag indicating unfamiliarity with the core JDK interface taxonomy. - ❌ Using
WeakHashMapas a general cache: Keys are purged during Minor GC as soon as external strong references vanish. - ❌ Iterating a map via
map.keySet()and callingmap.get(key)inside the loop: An anti-pattern that costs $2\times$ lookups; always iterate overmap.entrySet(). - ❌ Using
Hashtablein modern applications: Outdated, coarse synchronization bottleneck; replace withConcurrentHashMap.
Related Topics
- What is the difference between HashMap, LinkedHashMap and TreeMap — Comparative analysis of the core maps
- When to use TreeMap — Specialized use cases and navigation
- What is WeakHashMap — Garbage collector reference interactions
- What is ConcurrentHashMap — High-concurrency map mechanics
- How does ConcurrentHashMap ensure thread-safety — CAS and bucket locking