Java Collections - HashMap, TreeMap & LinkedHashMap Interview Questions (2026) Interview Questions | JiQuest

add

#

Java Collections - HashMap, TreeMap & LinkedHashMap Interview Questions (2026)

BACKEND INTERVIEW PREPARATION
Java Collections: HashMap, TreeMap & LinkedHashMap
Master every Map implementation in Java — internal bucket arrays, hashing, treeification, concurrent access patterns, and Java 8–21 API additions. 100+ interview questions covering beginner to principal-engineer level.
⏳ 45 min read 📝 100+ Q&As 🎯 Beginner to Advanced
⚡ Quick Reference
HashMap default capacity16 (always a power of 2)
HashMap load factor0.75 — resize triggers when size > capacity * 0.75
Treeify threshold8 — chain converts to Red-Black tree at 8 nodes (Java 8+)
Untreeify threshold6 — tree reverts to linked list when shrunk to 6 nodes
TreeMap orderingNatural order (Comparable) or Comparator; backed by Red-Black tree
LinkedHashMap order modesInsertion-order (default) or access-order (LRU cache)
ConcurrentHashMap Java 8CAS + synchronized on individual bucket heads; no segment locks
Null keysHashMap: one null key allowed; TreeMap / ConcurrentHashMap: none
Map.of() (Java 9+)Unmodifiable, no null keys/values, no guaranteed order
computeIfAbsentInserts only when key absent or mapped to null; thread-safe in CHM
HashMap Internal Structure (Java 8+)
put(key, value)
key.hashCode() spread
index = hash & (n-1)
Bucket[0]
Bucket[1]
Bucket[2]
...
Bucket[15]
chain (<8 nodes)
Node→Node→null
|
tree (≥8 nodes, Java 8+)
TreeNode (Red-Black)
size > threshold
resize: capacity*2, rehash all

Java Collections Map Interview Questions & Answers

Q1. What is the internal data structure used by HashMap in Java?

A: HashMap internally uses an array of Node<K,V>[] called the table (also referred to as the bucket array). Each Node holds a hash, a key, a value, and a reference to the next node (forming a singly-linked list for collision chaining). The default initial capacity is 16. When a single bucket's chain exceeds the treeify threshold (8 nodes) and the total table capacity is at least 64, the chain is converted to a Red-Black tree of TreeNode objects (Java 8+).

Q2. What is the default initial capacity and load factor of HashMap?

A: The default initial capacity is 16 and the default load factor is 0.75. The threshold at which a resize is triggered equals capacity * loadFactor, so for a fresh HashMap with defaults the first resize happens after 12 entries are inserted (16 * 0.75 = 12).

Q3. Why must HashMap capacity always be a power of 2?

A: The bucket index is computed as index = hash & (capacity - 1). When capacity is a power of 2, subtracting 1 gives a bitmask of all 1s (e.g., 16 - 1 = 15 = 0b1111). Bitwise AND with this mask is equivalent to modulo but is much faster. Any non-power-of-2 value would leave gaps in the distribution and break the uniform bucket distribution. The constructor always rounds up the requested capacity to the next power of 2.

Q4. How does HashMap compute the bucket index from a key?

A: HashMap first spreads the key's hashCode to protect against poor hash functions:

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

XORing the high 16 bits into the low 16 bits ensures that differences in upper bits (which would otherwise be ignored by the mask) still affect bucket placement. The bucket index is then (n - 1) & hash(key) where n is the current table length.

Q5. What is the hashCode and equals contract, and why is it critical for Map keys?

A: The Java contract states: if a.equals(b) is true, then a.hashCode() == b.hashCode() must also be true. The converse is not required (hash collisions are allowed). For HashMap keys this is critical because: (1) the bucket index is derived from hashCode, so two equal keys must land in the same bucket; (2) equals is then used to find the exact node within the chain/tree. If you override equals without overriding hashCode, two logically equal objects could end up in different buckets, causing get() to return null even though the key was inserted and producing duplicate entries.

Q6. Describe the full put() algorithm of HashMap step by step.

A:

  1. Compute hash = (key == null) ? 0 : key.hashCode() ^ (key.hashCode() >>> 16).
  2. If the table is null or length 0, call resize() to initialise it.
  3. Compute i = (n-1) & hash. If table[i] is null, create a new Node and assign it directly.
  4. Otherwise, inspect the existing head node: if hash and key match, record it as the node to update.
  5. If the head is a TreeNode, delegate to the tree's putTreeVal().
  6. Otherwise walk the linked list: if an existing node with matching hash+key is found, record it; if the end is reached, append a new Node. After appending, if the chain length >= TREEIFY_THRESHOLD (8), call treeifyBin().
  7. If a node was found (step 4 or 6), update its value and return the old value.
  8. Increment modCount. If ++size > threshold, call resize().

Q7. How does HashMap resize and rehash?

A: resize() doubles the capacity (newCap = oldCap << 1) and doubles the threshold. It then allocates a new array and re-distributes all existing nodes. For each bucket, nodes are placed into either the same index or index + oldCap — this works because the new bit added to the mask is either 0 (stays put) or 1 (shifts by oldCap). This split is done without recomputing hashCode; only the extra bit of the stored hash is tested. TreeNodes are split via TreeNode.split(); if the resulting tree has ≤ UNTREEIFY_THRESHOLD (6) nodes it reverts to a linked list.

Q8. What is the treeify threshold and why was it introduced in Java 8?

A: The treeify threshold is 8 nodes per bucket. Before Java 8, worst-case get() on a bucket with n collisions was O(n) because all entries formed a linked list. With Java 8, once a chain reaches 8 nodes (and total capacity >= 64), it is converted to a Red-Black tree, reducing worst-case lookup to O(log n). This mitigates hash-flooding DoS attacks where an adversary crafts keys that all hash to the same bucket. The untreeify threshold is 6, adding hysteresis to avoid oscillation between tree and list under alternating inserts/removes.

Q9. Does HashMap allow null keys and null values?

A: Yes. HashMap allows exactly one null key (stored at bucket 0, because hash(null) == 0) and any number of null values. This is a deliberate design choice that differs from Hashtable (which prohibits both) and TreeMap/ConcurrentHashMap (which prohibit null keys because ordering and CAS operations on null would throw NullPointerException).

Q10. Compare HashMap, Hashtable, and ConcurrentHashMap in detail.

Feature HashMap Hashtable ConcurrentHashMap
Thread safety Not thread-safe Fully synchronized (method level) Thread-safe (fine-grained locking)
Null keys 1 allowed Not allowed Not allowed
Null values Allowed Not allowed Not allowed
Iteration Fail-fast iterator Fail-fast Enumeration Weakly consistent iterator
Performance Best single-threaded Poor under contention Best multi-threaded
Inheritance AbstractMap Dictionary (legacy) AbstractMap
Java version Java 2 (1.2) Java 1.0 (legacy) Java 5 (1.5)
Locking granularity None Entire map Bucket head (CAS + sync)

Q11. How does ConcurrentHashMap achieve thread safety in Java 8+ without segment locking?

A: In Java 7, ConcurrentHashMap used a fixed array of Segment objects (each a reentrant lock) — by default 16 segments, each protecting 1/16 of the map. In Java 8 this was replaced with a more fine-grained approach: reads are lock-free using volatile reads of the table array and node values. Writes use CAS (Compare-And-Swap via Unsafe.compareAndSwapObject) when inserting into an empty bucket. If the bucket is non-empty, synchronized(f) is used on the head node f, locking only that single bucket. This allows up to n concurrent writers (one per bucket) compared to Java 7's 16-way concurrency.

Q12. What is a fail-fast iterator and how does HashMap implement it?

A: A fail-fast iterator throws ConcurrentModificationException if the map is structurally modified (any add or remove operation, not a value update) during iteration except through the iterator's own remove(). HashMap maintains a modCount field incremented on every structural change. When an iterator is created it captures the current modCount as expectedModCount. On each next() call, if modCount != expectedModCount it throws. This is a best-effort mechanism — it is not a guarantee against all race conditions in a multithreaded context.

Q13. What is a WeakHashMap and what are its typical use cases?

A: WeakHashMap stores keys as WeakReference objects. When a key is no longer strongly reachable (only the map's weak reference points to it), the garbage collector may collect it, and the entry is eventually removed from the map. This makes WeakHashMap ideal for: (1) caches where you want entries to be automatically evicted when the key object is GC'd; (2) metadata maps where you track information about objects without preventing their collection; (3) the JDK's own use in ThreadLocal internals. WeakHashMap is not thread-safe and does not support null keys. Note that after GC, stale entries are removed lazily during the next structural modification or size query, via a ReferenceQueue.

Q14. What is IdentityHashMap and when would you use it?

A: IdentityHashMap intentionally violates the Map contract by using reference equality (==) instead of equals() for key comparison, and System.identityHashCode(key) instead of key.hashCode(). This means two keys that are equals() but not the same object are treated as distinct keys. Use cases: (1) serialization / deep-copy utilities that need to track which object instances (not logical values) have already been visited; (2) weak interning tables; (3) any scenario where the object identity, not logical equality, is the discriminator. IdentityHashMap uses a linear-probe open-addressing scheme (not chaining) with a table that stores alternating key-value pairs.

Q15. What is EnumMap and why is it preferred over HashMap for enum keys?

A: EnumMap is a specialized Map implementation for use with enum keys. Internally it uses a plain array indexed by the enum's ordinal value. This gives O(1) for all operations with extremely low constant factors — no hashing, no collision handling, no boxing for keys. EnumMap maintains key insertion order equal to the enum's declaration order, is more memory-efficient than HashMap, and is not thread-safe. It does not allow null keys but allows null values. For any map keyed exclusively by an enum type, EnumMap is always the correct choice for performance.

Q16. Explain TreeMap's internal structure and ordering guarantees.

A: TreeMap is backed by a Red-Black tree, a self-balancing binary search tree. All keys are maintained in sorted order: natural ordering (if keys implement Comparable) or by a Comparator supplied at construction. Red-Black trees guarantee that the tree height is at most 2 * log2(n), so get, put, remove, and containsKey are all O(log n). TreeMap implements NavigableMap (extends SortedMap), providing rich navigation methods: floorKey(k), ceilingKey(k), higherKey(k), lowerKey(k), headMap(toKey), tailMap(fromKey), subMap(from,to), firstKey(), lastKey(), and their descending counterparts.

Q17. What interfaces does TreeMap implement?

A: TreeMap<K,V> implements: NavigableMap<K,V> (which extends SortedMap<K,V>, which extends Map<K,V>), Cloneable, and Serializable. It extends AbstractMap<K,V>. The SortedMap interface adds comparator(), firstKey(), lastKey(), headMap(), tailMap(), and subMap(). The NavigableMap interface adds lowerEntry(), floorEntry(), ceilingEntry(), higherEntry(), pollFirstEntry(), pollLastEntry(), and descendingMap().

Q18. Does TreeMap allow null keys or null values?

A: TreeMap does not allow null keys because ordering requires calling compareTo() or the Comparator, and any null key comparison would throw NullPointerException. Null values are permitted because value comparison is never needed for ordering or lookup. If you need null-key support with sorting, use a custom Comparator that handles nulls explicitly and create a TreeMap with it, but this is an unusual edge case.

Q19. Explain LinkedHashMap and how it maintains order.

A: LinkedHashMap extends HashMap and maintains a doubly-linked list running through all entries in the order they were inserted (by default) or in access order (when the constructor parameter accessOrder=true is used). The Node class is extended to LinkedHashMap.Entry which adds before and after pointers. This extra overhead allows iteration in predictable order while preserving the O(1) average complexity of HashMap operations. The linked list adds only constant overhead per put/get operation (pointer updates) and constant memory per entry (two extra references).

Q20. How can LinkedHashMap be used to implement an LRU cache?

A: Create a LinkedHashMap with accessOrder = true and override removeEldestEntry():

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;
    }
}

// Usage:
LRUCache<Integer, String> cache = new LRUCache<>(3);
cache.put(1, "one");
cache.put(2, "two");
cache.put(3, "three");
cache.get(1);      // moves key 1 to most-recently-used end
cache.put(4, "four"); // evicts key 2 (least recently used)

With accessOrder=true every get() and getOrDefault() call moves the accessed entry to the tail of the linked list (most recently used). removeEldestEntry() is called after every put(); returning true causes the eldest (head) entry to be removed.

Q21. What is the time complexity of HashMap operations in average and worst case?

Operation Average Case Worst Case (pre-Java 8) Worst Case (Java 8+)
get(key) O(1) O(n) O(log n)
put(key, value) O(1) O(n) O(log n)
remove(key) O(1) O(n) O(log n)
containsKey(key) O(1) O(n) O(log n)
iteration (entrySet) O(n + capacity) O(n + capacity) O(n + capacity)

Q22. What is the difference between entrySet(), keySet(), and values() iteration?

A: All three return views of the map, not copies. entrySet() returns a Set<Map.Entry<K,V>> — each Entry gives both key and value without an extra map lookup, making it the most efficient approach when you need both. keySet() returns a Set<K> — if you then call map.get(key) inside the loop you are doing an extra hash lookup per iteration. values() returns a Collection<V> — useful when you only need values and don't care about keys. All three iterators reflect structural changes to the map (fail-fast).

Q23. What is computeIfAbsent() and how does it differ from putIfAbsent()?

A: putIfAbsent(key, value) inserts only if the key is absent (or mapped to null), but the value is already computed (eager). computeIfAbsent(key, mappingFunction) invokes the function lazily only if the key is absent, then inserts the result. If the function returns null, no mapping is added. computeIfAbsent is useful for multi-map patterns:

Map<String, List<String>> groupBy = new HashMap<>();

// Old way — creates ArrayList even if key exists:
groupBy.putIfAbsent(key, new ArrayList<>());
groupBy.get(key).add(value);

// Java 8 — creates ArrayList only when needed:
groupBy.computeIfAbsent(key, k -> new ArrayList<>()).add(value);

In ConcurrentHashMap, computeIfAbsent is atomic — the function is invoked at most once per key under lock, preventing duplicate initialization under contention.

Q24. What is computeIfPresent() and what happens if the mapping function returns null?

A: computeIfPresent(key, remappingFunction) invokes the function only if the key has a non-null mapping, passing both the key and existing value. The returned value becomes the new mapping. If the function returns null, the mapping is removed from the map entirely (equivalent to calling remove). Example: incrementing a counter only if the entry exists: map.computeIfPresent("hits", (k, v) -> v + 1).

Q25. Explain the merge() method in Java 8 Map.

A: merge(key, value, remappingFunction) is designed for combining values. If the key is absent (or mapped to null), the provided value is inserted directly. If the key already has a non-null mapping, the remapping function is called with (existingValue, newValue) and the result becomes the new mapping. If the function returns null the entry is removed. Classic use case — word frequency counting:

Map<String, Integer> freq = new HashMap<>();
String[] words = {"java", "map", "java", "stream", "map", "java"};

for (String w : words) {
    freq.merge(w, 1, Integer::sum);
    // First occurrence: inserts (w, 1)
    // Subsequent: applies sum(existing, 1)
}
// Result: {java=3, map=2, stream=1}

Q26. What does getOrDefault() do and when should you use it?

A: getOrDefault(key, defaultValue) returns the value for the key if present, or the provided default value if the key is absent (or mapped to null). Unlike get(), it avoids a null check after the call. It does not modify the map. Example: int count = freq.getOrDefault(word, 0). Use it when a sentinel/default value is acceptable and you want concise null-safe reads without inserting anything.

Q27. What is compute() in Java 8 Map?

A: compute(key, remappingFunction) is the most general form — the function receives the key and the current value (which may be null if absent). The return value becomes the new mapping; returning null removes the entry. It is equivalent to a read-modify-write on the entry. Unlike computeIfAbsent/computeIfPresent, it always calls the function regardless of whether a mapping exists. Useful when the logic for "absent" and "present" cases is unified in one lambda.

Q28. How does Map.of() differ from new HashMap() and Map.copyOf() in Java 9+?

A: Map.of(k1,v1, k2,v2, ...) (up to 10 key-value pairs; use Map.ofEntries() for more) returns an unmodifiable map. It: (1) throws UnsupportedOperationException on any mutating operation; (2) does not allow null keys or values; (3) has no guaranteed iteration order; (4) is not serializable (pre-Java 19). Map.copyOf(map) also returns an unmodifiable copy, refusing nulls, but takes an existing map as input. A new HashMap is mutable, allows nulls, and uses the standard bucket implementation. In tests and configuration, Map.of is preferred for its brevity and safety; in production mutable code, HashMap or ConcurrentHashMap is appropriate.

Q29. What is the difference between putIfAbsent() and computeIfAbsent() in ConcurrentHashMap specifically?

A: In ConcurrentHashMap, putIfAbsent requires the value to be pre-constructed (even if it won't be used), while computeIfAbsent is atomic and lazy — the mapping function executes under the bucket lock, guaranteeing the function runs at most once per key even under heavy concurrent access. This is the correct pattern for initializing shared mutable values (e.g., creating a new list for a key) without the risk of two threads both initializing and one result being discarded.

Q30. What happens when you call HashMap.get() with a key that has been mutated after insertion?

A: This leads to incorrect behavior. If a mutable object is used as a key and its fields affecting hashCode() or equals() are changed after insertion, the map will try to find the key in the bucket determined by the new hash. Since the entry was placed at the bucket determined by the old hash, get() will return null even though the entry is in the table. The entry is effectively "lost." Keys must be immutable — or at minimum, their hash-relevant state must not change while they are in the map. This is why String, boxed types, and value-based records make the best keys.

Q31. Why is String a popular HashMap key?

A: String is immutable, so its hashCode never changes after creation. String also caches its hashCode after the first computation, so repeated use as a map key incurs only one hash calculation. String correctly implements both hashCode() (polynomial rolling hash over characters) and equals() (character-by-character comparison), satisfying the hashCode/equals contract. These properties make String the ideal, safe default for map keys in almost all Java applications.

Q32. How do you make a HashMap thread-safe without using ConcurrentHashMap?

A: Three options: (1) Collections.synchronizedMap(new HashMap<>()) wraps every method with synchronized on the returned map instance — compound operations still need external synchronization, and iteration must be synchronized manually. (2) Use ReadWriteLock with your own wrapper: ReentrantReadWriteLock allows concurrent reads but exclusive writes. (3) Copy-on-write approach: replace the entire map reference atomically via AtomicReference — suitable only for read-heavy, write-rare maps. All these are inferior to ConcurrentHashMap for general concurrent use.

Q33. What is the significance of the load factor in HashMap?

A: The load factor is the ratio of the number of entries to the bucket array size at which a resize is triggered: threshold = capacity * loadFactor. A lower load factor (e.g., 0.5) means fewer collisions and faster lookups but more memory usage and more frequent resizes. A higher load factor (e.g., 0.9) saves memory but increases average collision chain length and degrades performance. The default 0.75 is a well-tested empirical balance between time and space. For read-heavy caches where memory is plentiful, reducing the load factor improves throughput. For memory-constrained environments, increasing it (up to 1.0) is acceptable with a performance trade-off.

Q34. What happens if two keys have the same hashCode but are not equal?

A: This is a hash collision. Both entries land in the same bucket (same index in the array). HashMap handles this by chaining: the second entry is appended to the linked list (or inserted into the tree if already treeified) at that bucket. On lookup, after finding the correct bucket by hash, the map walks the chain and uses equals() to identify the exact entry. Good hash distribution minimizes collisions; a pathological hashCode that returns a constant would funnel all entries into one bucket, degrading performance to O(n) (or O(log n) with treeification).

Q35. What is the difference between HashMap.size() and HashMap.capacity()?

A: size() returns the number of key-value mappings currently in the map. capacity is the length of the internal bucket array and is not directly exposed through the public API (no capacity() method). You can observe capacity indirectly by comparing size to the threshold or via reflection. After creation with default settings, capacity = 16; after first resize, capacity = 32, etc. The relationship is: threshold = capacity * loadFactor, and a resize is triggered when size exceeds threshold.

Q36. How do you iterate a Map using Java 8 forEach?

A:

Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);

// Option 1: entrySet (most efficient when both key and value needed)
for (Map.Entry<String, Integer> e : map.entrySet()) {
    System.out.println(e.getKey() + " = " + e.getValue());
}

// Option 2: Java 8 forEach with BiConsumer
map.forEach((k, v) -> System.out.println(k + " = " + v));

// Option 3: keySet (extra lookup cost if value needed)
for (String key : map.keySet()) {
    System.out.println(key + " = " + map.get(key));
}

// Option 4: values only
for (int val : map.values()) {
    System.out.println(val);
}

Q37. How do you sort a HashMap by value?

A: HashMap does not maintain any order. To sort by value, extract entries and sort them: map.entrySet().stream().sorted(Map.Entry.comparingByValue()).collect(Collectors.toLinkedHashMap(...)). Or: map.entrySet().stream().sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())).forEach(...). The result must be collected into a LinkedHashMap to preserve sorted order since a new HashMap would lose it. Alternatively, use a TreeMap with a custom comparator, but note that TreeMap uses keys for ordering — sorting by value with a TreeMap comparator is tricky and can break the map invariants if two keys have equal values.

Q38. What is replaceAll() in Map (Java 8)?

A: replaceAll(BiFunction<K, V, V> function) replaces each entry's value with the result of applying the function to the key and current value. It is an in-place mutation, not a new map. Example: map.replaceAll((k, v) -> v * 2) doubles every value. It is equivalent to iterating over entrySet() and calling setValue() on each entry, but expressed more concisely.

Q39. How does TreeMap's subMap(), headMap(), and tailMap() work?

A: These methods return views (not copies) of portions of the map. Mutations to the view are reflected in the original map and vice versa. subMap(fromKey, toKey) returns entries with keys in [fromKey, toKey) — inclusive from, exclusive to by default (overloaded version takes booleans for inclusivity). headMap(toKey) returns entries strictly less than toKey. tailMap(fromKey) returns entries greater than or equal to fromKey. Structural modifications outside the view range through the original map are visible in the view. These views are backed by the same Red-Black tree; range queries are O(log n).

Q40. What are floorKey(), ceilingKey(), lowerKey(), and higherKey() in TreeMap?

A: These are NavigableMap navigation methods: floorKey(k) returns the greatest key less than or equal to k (or null). ceilingKey(k) returns the smallest key greater than or equal to k (or null). lowerKey(k) returns the greatest key strictly less than k (no equals). higherKey(k) returns the smallest key strictly greater than k (no equals). Each runs in O(log n). Their Entry counterparts (floorEntry, etc.) return the full key-value pair. These are invaluable for range queries, schedule lookups, and nearest-neighbor searches.

Q41. What is the difference between HashMap and LinkedHashMap for cache use cases?

A: HashMap provides no ordering guarantee, making it unsuitable for LRU eviction. LinkedHashMap with accessOrder=true maintains the access sequence, enabling O(1) LRU cache implementation by overriding removeEldestEntry(). The cost is two extra pointer updates per access (moving the accessed entry to the tail of the doubly-linked list) and two extra reference fields per entry. For a simple bounded cache, LinkedHashMap LRU is simpler than maintaining a separate doubly-linked list + HashMap structure manually.

Q42. Can ConcurrentHashMap guarantee atomic compound operations?

A: Individual operations like put(), get(), remove() are atomic. However, compound operations like check-then-act (e.g., if (!map.containsKey(k)) map.put(k, v)) are NOT atomic unless you use the atomic methods provided: putIfAbsent(), computeIfAbsent(), compute(), merge(), replace(key, oldVal, newVal) (conditional replace). Always prefer these atomic compound methods over separate reads and writes in concurrent contexts.

Q43. What is the mappingCount() method in ConcurrentHashMap vs size()?

A: size() returns an int and is limited to Integer.MAX_VALUE. mappingCount() (added in Java 8) returns a long, supporting very large maps with more than ~2 billion entries. In ConcurrentHashMap, both size() and mappingCount() may return an estimate rather than an exact count at the moment of the call, because concurrent modifications may be occurring. The count is maintained via a CounterCell striped counter (inspired by Striped64) to reduce contention during concurrent increments.

Q44. Explain ConcurrentHashMap's forEach(), reduce(), and search() bulk operations.

A: Java 8 added parallel bulk operations to ConcurrentHashMap: forEach(parallelismThreshold, action) performs an action on each entry. reduce(parallelismThreshold, transformer, reducer) reduces all entries to a single value in parallel. search(parallelismThreshold, searchFunction) applies a function to each entry and returns the first non-null result, short-circuiting when found. The parallelismThreshold controls when the common ForkJoinPool is used (if the map has fewer elements than the threshold, operations run sequentially). These are useful for analytics over large shared maps without external coordination.

Q45. What is the difference between remove(key) and remove(key, value) in Map?

A: remove(key) removes the entry for the key unconditionally and returns the old value (or null). remove(key, value) (added in Java 8) is a conditional remove — it removes the entry only if the key is currently mapped to the specified value, returning a boolean. The conditional form is especially useful in ConcurrentHashMap for atomic compare-and-remove operations: map.remove(key, expectedValue) atomically removes only if the value matches, preventing race conditions.

Q46. How does HashMap handle the case where the table capacity is less than 64 when a chain reaches 8 nodes?

A: Treeification has a minimum table capacity requirement: MIN_TREEIFY_CAPACITY = 64. If a bucket's chain reaches 8 nodes but the overall table capacity is less than 64, HashMap performs a resize() instead of treeifying. The rationale is that with a small table, many collisions are expected simply due to the limited number of buckets, and doubling the capacity will spread the entries rather than turning a small table's chains into trees unnecessarily. Treeification only occurs when both conditions are met: chain length >= 8 AND table length >= 64.

Q47. What is the performance impact of a poor hashCode() implementation?

A: A poor hashCode that returns a constant (e.g., always 0) puts every key in the same bucket. Before Java 8 this degrades all operations to O(n); with Java 8+ treeification it degrades to O(log n) — still far worse than the O(1) average. A hashCode that uses only part of the object's state (e.g., only the first character of a String) creates clustering in a few buckets. The result is increased collision chains, excessive equals() calls, and potential CPU cache thrashing. The spread function in HashMap mitigates poor high-bit distribution but cannot fix low-entropy hashCodes that always produce the same low-order bits.

Q48. How do you create an immutable Map in Java before Java 9?

A: Pre-Java 9: Collections.unmodifiableMap(new HashMap<>(source)). This returns a wrapper that throws UnsupportedOperationException on any mutating call. However, the underlying HashMap is still referenced and if a caller retains a reference to it they can still modify it. To prevent that, pass an anonymous or immediately-created map: Collections.unmodifiableMap(new HashMap<>() {{ put("a", 1); put("b", 2); }}) (double-brace initialization — not recommended for production due to anonymous class overhead). In Java 9+, prefer Map.of() or Map.copyOf().

Q49. What is double-brace initialization for Maps and why is it discouraged?

A: Double-brace initialization creates an anonymous subclass of HashMap with an instance initializer: new HashMap<>() {{ put("a", 1); }}. It is concise but has serious drawbacks: (1) creates a new class file per usage, bloating the classpath; (2) the anonymous class holds an implicit reference to the enclosing outer instance, causing memory leaks if the map outlives the enclosing object; (3) breaks serialization if the enclosing class is not serializable; (4) fails equals() comparisons if the other map is a plain HashMap (different class). Use Map.of() (Java 9) or a stream collector instead.

Q50. How does HashMap handle Integer keys vs String keys in terms of hashing?

A: Integer.hashCode() returns the integer value itself, which is excellent for sequential keys (0, 1, 2, ...) since they are uniformly distributed. However, for keys like powers of 2 (1, 2, 4, 8, ...) the low-order bits are always 0, which would cluster them into few buckets without the spread function. HashMap's hash(key) XORs the upper 16 bits into the lower 16 bits, fixing this. String.hashCode() uses a polynomial: s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1], which distributes well across typical identifier strings.

Q51. What is the relationship between Map, SortedMap, and NavigableMap interfaces?

A: The hierarchy is: Map <- SortedMap <- NavigableMap. Map is the base interface defining put/get/remove/entrySet etc. SortedMap adds contract that keys are always in sorted order and provides firstKey(), lastKey(), headMap(), tailMap(), subMap(), comparator(). NavigableMap extends SortedMap with closest-match navigation: floor, ceiling, lower, higher, descending views, and poll operations. TreeMap implements NavigableMap; ConcurrentSkipListMap also implements NavigableMap and is thread-safe.

Q52. When would you choose TreeMap over HashMap?

A: Choose TreeMap when: (1) you need keys in sorted order for iteration or range queries; (2) you need navigation operations (floor, ceiling, subMap); (3) you need to find the min/max key efficiently; (4) you need a consistent ordering for reproducible output (e.g., serialization, debugging). Choose HashMap when: you only need fast O(1) key lookup/insert/delete and order is irrelevant. TreeMap's O(log n) operations are slower than HashMap's O(1) average, so prefer HashMap for pure lookup scenarios. For concurrent sorted maps, use ConcurrentSkipListMap (implements NavigableMap, thread-safe, O(log n) average).

Q53. What is ConcurrentSkipListMap and how does it compare to ConcurrentHashMap?

A: ConcurrentSkipListMap is a thread-safe implementation of NavigableMap backed by a skip list (probabilistic data structure with O(log n) average operations). It maintains keys in sorted order, unlike ConcurrentHashMap which has no order guarantee. ConcurrentSkipListMap uses non-blocking CAS-based algorithms (no locks), making it suitable for high-concurrency scenarios where order is required. ConcurrentHashMap has O(1) average operations and is preferred when sorted order is not needed. ConcurrentSkipListMap is ideal for: leaderboards, event queues ordered by timestamp, range-scan-heavy workloads.

Q54. How does TreeMap perform Red-Black tree rebalancing on insertion?

A: A Red-Black tree satisfies: (1) every node is red or black; (2) the root is black; (3) red nodes have black children (no two consecutive reds); (4) all paths from a node to its null leaves have the same number of black nodes. On insertion, the new node is colored red. If a red violation occurs (red parent), TreeMap applies color flips and rotations (left or right rotation on the parent/grandparent). The algorithm follows Cormen's CLRS RB-INSERT-FIXUP: if uncle is red, recolor; if uncle is black, rotate. At most 2 rotations are needed per insertion, keeping the tree balanced in O(log n).

Q55. What is the memory overhead of a HashMap entry compared to a simple array?

A: Each HashMap.Node object in a 64-bit JVM consumes approximately: 16 bytes (object header) + 4 bytes (hash int) + 4 bytes (key reference) + 4 bytes (value reference) + 4 bytes (next pointer) = ~32 bytes, plus the actual key and value objects. Compare this to a plain array element: 4 bytes (reference) + the object. For a map with n entries and capacity c, the total overhead includes the Node array of c references (8 bytes each on modern JVMs) plus 32 bytes per Node, plus the key/value objects themselves. For integer keys/values, using a specialized library like Eclipse Collections' IntIntHashMap (primitive arrays) eliminates boxing and reduces overhead by 70-80%.

Q56. How does WeakHashMap use ReferenceQueue for cleanup?

A: WeakHashMap holds each key as a WeakReference<K> registered with an internal ReferenceQueue. When the GC collects a key, the corresponding WeakReference is enqueued in the ReferenceQueue. WeakHashMap polls this queue at the start of any size() call or structural modification and removes all stale entries (entries whose WeakReference has been enqueued). This means stale entries are not removed immediately upon GC; they persist until the next interaction with the map. A long-idle WeakHashMap may therefore hold stale entries in memory until the next operation.

Q57. What is the difference between HashMap.clone() and copying a HashMap via its copy constructor?

A: Both perform a shallow copy: the new map contains the same key and value object references. clone() returns an Object and requires a cast; it also preserves the load factor and initial capacity. The copy constructor new HashMap<>(existingMap) is the idiomatic approach and is clearer in intent. Neither clones the keys or values themselves — if values are mutable, modifications through one map's values will be visible through the other. For a deep copy, you must iterate and clone each key/value individually.

Q58. How does HashMap behave during concurrent access without synchronization?

A: Concurrent unsynchronized access can cause: (1) lost updates — two threads both compute the next pointer, and one's write overwrites the other's; (2) in Java 7, concurrent resize could create a circular linked list, causing get() to loop infinitely (the famous HashMap infinite loop bug — fixed in Java 8 by using separate high/low lists during resize); (3) partial writes leading to NullPointerException or ArrayIndexOutOfBoundsException; (4) inconsistent size(). Never use HashMap in a multi-threaded context without external synchronization or use ConcurrentHashMap.

Q59. What is the Java 7 HashMap infinite loop bug?

A: In Java 7's resize(), when two threads simultaneously trigger a rehash, the linked list reversal in transfer() could create a cycle in the chain. Thread A and Thread B both reach the resize condition. Thread A completes the resize and reverses the chain for a bucket. Thread B then continues from where it stopped, seeing the reversed chain and creating a circular reference (A.next = B, B.next = A). Subsequent get() calls on that bucket enter an infinite loop. Java 8 fixed this by maintaining two separate head/tail pointers (lo/hi lists) for each bucket during resize, never reversing the chain.

Q60. How do you find all entries with a specific value in a HashMap?

A: HashMap does not index by value; finding entries by value is O(n): map.entrySet().stream().filter(e -> Objects.equals(e.getValue(), targetValue)).collect(Collectors.toList()). If reverse lookups are frequent, maintain a second Map from values to keys, or use a Guava BiMap. For one-to-one mappings, a BiMap enforces uniqueness of both keys and values and provides O(1) inverse lookup via inverse().

Q61. What is the NavigableMap.pollFirstEntry() and pollLastEntry()?

A: These methods atomically retrieve and remove the entry with the smallest (or largest) key. pollFirstEntry() returns and removes the Map.Entry with the lowest key; pollLastEntry() does the same for the highest key. Both return null if the map is empty. These are used in priority-queue-like patterns (e.g., job scheduling by priority/time where TreeMap keyed by timestamp serves as the queue). For thread-safe usage, ConcurrentSkipListMap provides thread-safe equivalents.

Q62. What is the difference between SortedMap.headMap() being exclusive vs NavigableMap.headMap(key, inclusive)?

A: The SortedMap.headMap(toKey) inherited method always creates an exclusive upper bound (strictly less than toKey). The NavigableMap.headMap(toKey, boolean inclusive) overload added in Java 6 allows specifying whether the bound is inclusive or exclusive. The same applies to tailMap(fromKey, boolean inclusive) and subMap(fromKey, fromInclusive, toKey, toInclusive). This flexibility is critical for range scan APIs where half-open vs. closed intervals matter (e.g., date range queries).

Q63. How does Map.Entry.comparingByKey() work and where is it useful?

A: Map.Entry.comparingByKey() (Java 8) returns a Comparator<Map.Entry<K,V>> that orders entries by their natural key order. comparingByKey(Comparator<K>) accepts a custom comparator. Similarly, comparingByValue() orders by value. These are used in stream pipelines: map.entrySet().stream().sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())).forEach(...). They eliminate the verbose lambda (e1, e2) -> e1.getValue().compareTo(e2.getValue()).

Q64. How can you group a list of objects into a Map using Collectors.groupingBy()?

A:

List<Employee> employees = getEmployees();

// Group by department -> produces Map<String, List<Employee>>
Map<String, List<Employee>> byDept =
    employees.stream()
             .collect(Collectors.groupingBy(Employee::getDepartment));

// Group and count per department -> Map<String, Long>
Map<String, Long> countByDept =
    employees.stream()
             .collect(Collectors.groupingBy(
                 Employee::getDepartment,
                 Collectors.counting()));

// Group with a downstream TreeMap (sorted departments)
Map<String, List<Employee>> sortedByDept =
    employees.stream()
             .collect(Collectors.groupingBy(
                 Employee::getDepartment,
                 TreeMap::new,
                 Collectors.toList()));

Q65. What is Collectors.toMap() and what happens on duplicate keys?

A: Collectors.toMap(keyMapper, valueMapper) collects a stream into a Map. If two elements produce the same key, it throws IllegalStateException: Duplicate key. To handle duplicates, provide a merge function as the third argument: Collectors.toMap(keyMapper, valueMapper, (v1, v2) -> v1) (keep first) or (v1, v2) -> v2 (keep last). A fourth argument specifies the map supplier: Collectors.toMap(..., LinkedHashMap::new) to maintain insertion order.

Q66. What is the difference between Map.entry() and Map.Entry in Java 9+?

A: Map.entry(k, v) (Java 9+) creates an immutable Map.Entry — useful with Map.ofEntries(): Map.ofEntries(Map.entry("a",1), Map.entry("b",2)). It throws NullPointerException for null keys or values. Map.Entry is the nested interface defined since Java 2 — its concrete mutable implementation (HashMap.Node) is obtained via entrySet().iterator(). Prior to Java 9, you'd use new AbstractMap.SimpleEntry<>(k, v) or new AbstractMap.SimpleImmutableEntry<>(k, v) to create standalone entries.

Q67. How does ConcurrentHashMap's size() work internally in Java 8+?

A: ConcurrentHashMap maintains a base count plus an array of CounterCell objects (inspired by Striped64 / LongAdder). When a thread increments the count and there is no contention, it updates the base count via CAS. Under high contention, threads are hashed to individual CounterCells in the array and update them independently. size() sums the base count and all CounterCell values. This striped counting minimizes CAS failures under concurrent updates. The result is an estimate that may not reflect in-flight changes, but is accurate enough for most use cases.

Q68. What is the initial segment count in ConcurrentHashMap Java 7 and can it be changed?

A: In Java 7, the default concurrency level (number of segments) is 16, meaning up to 16 threads can write to different segments simultaneously without contention. It can be set via the constructor: new ConcurrentHashMap<>(initialCapacity, loadFactor, concurrencyLevel). The concurrencyLevel is rounded up to the nearest power of 2. Setting it higher (e.g., 32 or 64) reduces lock contention for write-heavy workloads but increases memory overhead (more Segment objects). In Java 8, this parameter is retained for backward compatibility but is effectively ignored since segment locking was replaced.

Q69. How would you implement a frequency counter map using Java 8 APIs?

A:

// Option 1: merge
Map<String, Integer> freq = new HashMap<>();
for (String word : words) {
    freq.merge(word, 1, Integer::sum);
}

// Option 2: compute
for (String word : words) {
    freq.compute(word, (k, v) -> v == null ? 1 : v + 1);
}

// Option 3: getOrDefault + put
for (String word : words) {
    freq.put(word, freq.getOrDefault(word, 0) + 1);
}

// Option 4: Collectors.frequency via streams
Map<String, Long> freqLong =
    Arrays.stream(words)
          .collect(Collectors.groupingBy(w -> w, Collectors.counting()));

Q70. What is the difference between replace(key, value) and replace(key, oldValue, newValue)?

A: replace(key, value) replaces the value for the key only if it currently has any mapping; returns the old value or null. It is unconditional as long as the key exists. replace(key, oldValue, newValue) (conditional) replaces only if the current value equals oldValue; returns a boolean indicating success. The conditional form is the Map equivalent of CAS (Compare-And-Swap) and is particularly important in ConcurrentHashMap for optimistic locking patterns.

Q71. What are the differences in iteration order between HashMap, LinkedHashMap, and TreeMap?

A: HashMap: no guaranteed order; iteration order can change after resize. LinkedHashMap (insertion order): entries are iterated in the order they were first inserted. LinkedHashMap (access order): entries are iterated from least-recently-accessed to most-recently-accessed. TreeMap: entries are iterated in ascending natural order of keys (or Comparator order). EnumMap: entries in enum declaration order. Understanding these differences is critical when debugging serialization, caching, and test assertions.

Q72. How do you atomically update a value in ConcurrentHashMap?

A:

ConcurrentHashMap<String, AtomicInteger> map = new ConcurrentHashMap<>();

// Atomic increment using computeIfAbsent + AtomicInteger
map.computeIfAbsent("hits", k -> new AtomicInteger(0)).incrementAndGet();

// Or using merge for simple int counting (atomic in CHM):
ConcurrentHashMap<String, Integer> counts = new ConcurrentHashMap<>();
counts.merge("hits", 1, Integer::sum); // atomic read-modify-write

// Or using compute (fully atomic):
counts.compute("hits", (k, v) -> (v == null) ? 1 : v + 1);

Both merge() and compute() are atomic in ConcurrentHashMap — the remapping function executes under the bucket lock, preventing lost updates.

Q73. What is a NavigableMap.descendingMap() and how is it implemented in TreeMap?

A: descendingMap() returns a view of the map with all mappings in reverse key order. In TreeMap, this is implemented as a DescendingSubMap — a view backed by the same Red-Black tree but with reversed comparator logic. All navigation operations (firstKey, lastKey, ceiling, floor) are inverted. Iteration over descendingMap().entrySet() walks the Red-Black tree in reverse in-order (right subtree first). The view is live — changes to the original TreeMap are reflected immediately. No data is copied; O(1) to create the view.

Q74. Can you use a lambda as a Comparator in TreeMap?

A: Yes. Comparator is a functional interface. For case-insensitive string keys: new TreeMap<>(String.CASE_INSENSITIVE_ORDER). For custom ordering: new TreeMap<>((a, b) -> b.length() - a.length()) (reverse by length). Important: if the Comparator is inconsistent with equals, the TreeMap will behave unexpectedly as a Map (two keys that Comparator says are equal share the same slot, even if equals() disagrees). The Comparator should be consistent with equals for correct Map semantics.

Q75. What is the serialization behavior of HashMap?

A: HashMap implements Serializable. The bucket array is declared transient — it is not serialized directly. Instead, HashMap implements custom writeObject() and readObject() methods that serialize the size, load factor, capacity (threshold), and then each key-value pair individually. On deserialization, the table is rebuilt from scratch by calling putVal() for each pair. This ensures that hash codes computed in a different JVM or after serialization are used (since hashCode may differ across JVMs for String in some older JDKs), and that the capacity is correctly restored.

Q76. What is Guava's ImmutableMap and how does it differ from Map.of()?

A: Both produce unmodifiable maps. Key differences: (1) ImmutableMap.of() and its builder support up to any number of entries (no 10-pair limit); (2) ImmutableMap throws on duplicate keys during building; (3) ImmutableMap preserves insertion order (like LinkedHashMap); (4) ImmutableMap has a separate ImmutableMap.Builder for incremental construction; (5) ImmutableMap predates Java 9 and works on Java 6+. Map.of() (Java 9+): random iteration order, max 10 direct params (use Map.ofEntries for more), part of the JDK (no dependency). For new Java 9+ code, Map.of() is preferred; for Guava-heavy codebases or when insertion order matters, use ImmutableMap.

Q77. How do you check if a Map contains a specific value efficiently?

A: Map.containsValue(value) always runs O(n) for HashMap and LinkedHashMap — it scans all buckets and chains. TreeMap also O(n) for containsValue. There is no built-in O(1) reverse lookup. For frequent value lookups, maintain a second Map (value -> key), use Guava's BiMap, or use a custom data structure. Note that containsKey() is O(1) average for HashMap.

Q78. What is the putAll() method and when does it trigger a resize?

A: putAll(Map m) copies all entries from m into the receiving map. Internally it calls putMapEntries(m, true) which first checks if a resize is needed for the combined size and may resize before copying. It then calls putVal() for each entry. This is more efficient than calling put() in a loop because: (1) a single resize may be done upfront rather than incremental resizes; (2) the initial capacity is set to fit all incoming entries. For initializing a map from another, use the copy constructor: new HashMap<>(otherMap) which also handles the initial sizing.

Q79. What does HashMap.clear() do internally?

A: clear() sets all entries in the table array to null and sets size to 0. It does NOT shrink the capacity — the bucket array remains at its current size. This means if a HashMap grew to capacity 1024 and you call clear(), the array stays at length 1024. If you need to reclaim memory, you must either create a new HashMap or (in some implementations) rebuild the map from scratch. modCount is incremented, which invalidates any active iterators.

Q80. What is the difference between Map.isEmpty() and Map.size() == 0?

A: Semantically they are equivalent. Preferring isEmpty() is a coding style recommendation (Effective Java Item 49: Check parameters for validity — use isEmpty for clarity). However, for HashMap and most Map implementations, isEmpty() simply returns size == 0, making them functionally identical in terms of performance. In concurrent collections, isEmpty()) may have slightly different semantics (ConcurrentHashMap's isEmpty() sums CounterCell values), but for most practical purposes they behave the same.

Q81. What is the behavior of Map.Entry.setValue() during iteration?

A: Map.Entry.setValue(newValue) called on an entry obtained from entrySet().iterator()` or `entrySet().forEach() updates the value directly in the map — it is not a copy. This is a safe way to update values during iteration without a ConcurrentModificationException (since it doesn't change the map's structure/modCount, only a value). You must not add or remove entries during iteration. This pattern is more efficient than removing and re-inserting for value-only updates.

Q82. How does TreeMap handle Comparable vs Comparator key ordering?

A: If no Comparator is provided at construction, TreeMap uses natural ordering: keys must implement Comparable<K>, and compareTo() is used. If a Comparator is provided, it takes precedence over Comparable. If neither is applicable (keys don't implement Comparable and no Comparator is given), a ClassCastException is thrown on the first insertion of a second key. The internal Entry comparison in compare(Object k1, Object k2) checks for a comparator first; if null, casts key to Comparable.

Q83. What are the differences between HashMap and EnumMap performance?

A: EnumMap uses a contiguous array indexed by ordinal, resulting in: (1) no hashing — O(1) lookup is a direct array index; (2) no collision handling; (3) no boxing overhead for the key; (4) better cache locality (sequential array access vs. scattered Node objects); (5) smaller memory footprint (no Node objects, no next pointers, no hash fields). Benchmarks typically show EnumMap is 2-5x faster than HashMap for enum-keyed maps. The trade-off is that EnumMap is less flexible (only enum keys) and its capacity is fixed to the number of enum constants.

Q84. How can you merge two Maps in Java 8?

A:

Map<String, Integer> map1 = new HashMap<>(Map.of("a", 1, "b", 2));
Map<String, Integer> map2 = new HashMap<>(Map.of("b", 20, "c", 3));

// Option 1: putAll (map2 values overwrite map1 on collision)
Map<String, Integer> merged = new HashMap<>(map1);
merged.putAll(map2);

// Option 2: merge with custom conflict resolution
Map<String, Integer> merged2 = new HashMap<>(map1);
map2.forEach((k, v) -> merged2.merge(k, v, Integer::sum));
// Result: {a=1, b=22, c=3}

// Option 3: Stream
Map<String, Integer> merged3 = Stream.of(map1, map2)
    .flatMap(m -> m.entrySet().stream())
    .collect(Collectors.toMap(
        Map.Entry::getKey,
        Map.Entry::getValue,
        Integer::sum));

Q85. What is a SoftReference map and how does it differ from WeakHashMap?

A: WeakHashMap uses weak references for keys, so entries are eligible for collection at any GC cycle if not otherwise strongly referenced. A SoftReference-based map (no JDK standard class — typically provided by libraries like Guava's CacheBuilder) uses soft references for values. Soft references are collected only when the JVM is under memory pressure (before OutOfMemoryError). Soft-value caches are better for caching expensive-to-compute values: the cache retains values as long as memory is available, releasing them only under pressure. Guava's CacheBuilder.newBuilder().softValues().build() provides this behavior with proper thread safety.

Q86. How do you handle null values in computeIfAbsent / computeIfPresent / merge?

A: computeIfAbsent(k, fn): if fn returns null, no mapping is inserted (the key remains absent). computeIfPresent(k, fn): if fn returns null, the existing mapping is removed. merge(k, v, fn): if fn returns null, the mapping is removed. compute(k, fn): if fn returns null, the mapping is removed. These null-return-means-remove semantics are consistent across all functional Map methods and allow cleanup as part of the same operation, but they can surprise developers expecting an explicit remove call.

Q87. How does HashMap behave when the initial capacity is set too low?

A: If initial capacity is too low, the map will trigger frequent resizes as entries are added. Each resize: (1) doubles the array; (2) iterates all existing entries and re-places them. With n entries and m resizes, the total work is O(n * m) amortized — though each resize is O(current size), the overall amortized cost of all inserts is still O(n). The danger is GC pressure: each resize allocates a new array, potentially leaving the old one for GC. If you know the expected size upfront, initialize with: new HashMap<>(expectedSize * 4 / 3 + 1) to set capacity such that no resize occurs. Guava's Maps.newHashMapWithExpectedSize(n) does this calculation for you.

Q88. What is the significance of the modCount field in HashMap?

A: modCount is an int counter incremented on every structural modification (put, remove, clear, resize). Iterators capture the current modCount as expectedModCount at creation. On each next()/hasNext()/remove() call, if modCount != expectedModCount a ConcurrentModificationException is thrown. This is the fail-fast mechanism. modCount is also used by Spliterator for the same purpose. Note: only the iterator's own remove() method (not map.remove()) increments expectedModCount to allow safe removal during iteration.

Q89. Can you store an array as a HashMap key? What are the pitfalls?

A: You can store an array as a HashMap key, but it is almost always wrong. Arrays inherit hashCode() from Object (identity-based: each array instance has a unique hash) and equals() from Object (reference equality). This means two different array objects with identical contents will be treated as different keys. To use arrays as keys by content, wrap them in a class that overrides hashCode/equals, use Arrays.asList() to convert to a List (which has content-based hashCode/equals), or use Arrays.toString(arr) as the key.

Q90. How does TreeMap.firstEntry() differ from TreeMap.firstKey()?

A: firstKey() returns only the key of the smallest entry; throws NoSuchElementException if the map is empty. firstEntry() returns the complete Map.Entry<K,V> (key + value) of the smallest entry; returns null if empty. Similarly, pollFirstEntry() removes and returns the first entry, whereas there is no pollFirstKey(). In code where you need both the key and value for the smallest element, use firstEntry() to avoid two separate lookups.

Q91. What is the difference between HashMap and Properties?

A: Properties extends Hashtable<Object,Object> (not HashMap). It is thread-safe (synchronized methods). It is designed for String key-value configuration and supports I/O via load(Reader) and store(Writer, comments) for .properties files, and XML via loadFromXML()/storeToXML(). Because it extends Hashtable with Object types, it technically allows non-String keys/values via the inherited methods, which is a design flaw — always use setProperty(String, String) and getProperty(String). Prefer HashMap for in-memory usage; use Properties only for configuration file I/O.

Q92. How do you convert a Map to a List of Map.Entry objects?

A: List<Map.Entry<K,V>> entries = new ArrayList<>(map.entrySet()). This creates a list from the entry set view. The entries in the list are live Entry objects from the map — calling setValue() on them modifies the map. To sort: entries.sort(Map.Entry.comparingByValue()). For an unmodifiable snapshot: List.copyOf(map.entrySet()) (Java 10+).

Q93. How does Guava's Multimap differ from Map<K, List<V>>?

A: A Guava Multimap<K,V> maps each key to one or more values, similar to Map<K, Collection<V>>. Key advantages over a raw Map of lists: (1) put(k, v) appends naturally without checking for null; (2) get(k) always returns a collection (never null); (3) size() returns total value count, not key count; (4) specialized subtypes: ArrayListMultimap, HashMultimap, TreeMultimap (keys sorted), LinkedListMultimap (preserves value insertion order per key). The manual Map-of-lists pattern requires boilerplate null checks and list creation; Multimap encapsulates all of this.

Q94. What is the behavior of ConcurrentHashMap.putIfAbsent() vs computeIfAbsent() under high concurrency?

A: putIfAbsent(k, v) requires the value to be constructed before the call. Under contention where multiple threads call putIfAbsent for the same key simultaneously, only one wins but all threads have already constructed the value object — the losing values are discarded. For expensive-to-construct values (DB connections, complex objects), this wastes resources. computeIfAbsent(k, fn) executes the mapping function under the bucket lock — at most one thread will ever execute the function per key, and only when the key is actually absent. This makes computeIfAbsent strictly superior for expensive value initialization in concurrent contexts.

Q95. How does Java's record type affect HashMap key usage?

A: Java records (Java 16+) automatically generate hashCode() and equals() based on all record components. Records are also immutable (final fields). This makes records ideal HashMap keys: (1) no risk of mutation breaking hash-bucket placement; (2) correct equality semantics automatically; (3) concise syntax. Example: record Point(int x, int y) {} can be used as a key in a HashMap and two new Point(1, 2) objects will be treated as equal with the same hash code.

Q96. What is the difference between Map.forEach() and entrySet().forEach()?

A: map.forEach(BiConsumer<K,V>) accepts a BiConsumer taking key and value directly. map.entrySet().forEach(Consumer<Map.Entry<K,V>>) accepts a Consumer of Entry objects. Both iterate all entries once. Map.forEach is typically simpler and slightly more efficient as it avoids wrapping in Entry objects. In ConcurrentHashMap, forEach is the parallel bulk operation method (with parallelismThreshold) which is different from the inherited default method — be aware which overload you are calling.

Q97. What is the Java 17+ MapEntry pattern for record keys in interviews?

A: A common interview question is to count islands or build a coordinate map. With records:

// Coordinate counting using record as key (Java 16+)
record Cell(int row, int col) {}

Map<Cell, Integer> visitCount = new HashMap<>();
visitCount.merge(new Cell(0, 0), 1, Integer::sum);
visitCount.merge(new Cell(0, 0), 1, Integer::sum);
visitCount.merge(new Cell(1, 1), 1, Integer::sum);

System.out.println(visitCount); // {Cell[row=0, col=0]=2, Cell[row=1, col=1]=1}
// Records provide correct hashCode + equals automatically

Q98. How do you implement a thread-safe read-heavy map with a ReadWriteLock?

A:

public class ReadHeavyMap<K, V> {
    private final Map<K, V> map = new HashMap<>();
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final Lock readLock = rwLock.readLock();
    private final Lock writeLock = rwLock.writeLock();

    public V get(K key) {
        readLock.lock();
        try { return map.get(key); }
        finally { readLock.unlock(); }
    }

    public void put(K key, V value) {
        writeLock.lock();
        try { map.put(key, value); }
        finally { writeLock.unlock(); }
    }
}
// ReadWriteLock allows N concurrent readers or 1 exclusive writer.
// Preferred over synchronized for 80%+ read workloads.

Q99. What is CopyOnWriteMap and does Java provide one?

A: Java does not provide a CopyOnWriteMap in the standard library (unlike CopyOnWriteArrayList). On every write, a copy-on-write map would create a new copy of the entire backing structure, making reads lock-free (no synchronization needed) but writes O(n). This is suitable for maps that are read very frequently but updated very rarely (e.g., routing tables, configuration maps read by thousands of threads). In practice, you can simulate it with AtomicReference<Map<K,V>> holding an unmodifiable map and replacing the reference on write. Libraries like Akka provide CopyOnWriteHashMap implementations.

Q100. What are the most common interview coding tasks involving HashMap?

A: The most frequent coding patterns: (1) Two Sum — store seen values in a map: map.put(num, i), check map.containsKey(target - num); (2) Group AnagramsgroupBy by sorted string key; (3) LRU Cache — LinkedHashMap with accessOrder; (4) Word frequencymerge(word, 1, Integer::sum); (5) Longest consecutive sequence — store all numbers in HashSet, walk chains; (6) Isomorphic strings / pattern matching — dual maps for bidirectional char mapping; (7) First non-repeating character — LinkedHashMap preserves insertion order for first-seen; (8) Subarray sum equals K — prefix sum + frequency map.

Q101. How do you detect if a HashMap is being used correctly as a cache vs a data store?

A: Key indicators of cache misuse: (1) unbounded growth — no eviction policy, map grows indefinitely → use LinkedHashMap LRU or Caffeine/Guava Cache; (2) no TTL — stale entries never expire → caching libraries provide expireAfterWrite/expireAfterAccess; (3) manual null checks instead of computeIfAbsent; (4) HashMap used in multi-threaded contexts without synchronization (should be ConcurrentHashMap or a proper cache). As a data store: HashMap is fine for bounded, in-memory, single-threaded lookups. The distinction matters for code review — a HashMap serving as a cache in a long-lived service is a reliability and memory issue.

Q102. Explain the happens-before guarantee for ConcurrentHashMap.

A: ConcurrentHashMap provides the following happens-before relationships: a successful put(k, v) by thread A happens-before a subsequent get(k) by thread B that returns v (or a later value). The volatile reads/writes of the table array and node values (CAS operations) establish happens-before via the JMM (Java Memory Model). However, there is no happens-before between a put and a concurrent get for the same key — the get may see either the old or new value, and either is correct behavior (weakly consistent). This is why ConcurrentHashMap is not suitable for use cases requiring strict sequential consistency across multiple operations.

Q103. What is the difference between Map.of() returning UnsupportedOperationException and Collections.emptyMap()?

A: Collections.emptyMap() returns a singleton instance of a typesafe empty map — it throws UnsupportedOperationException on any mutating operation. It is the right choice when you need to return or pass an empty immutable map. Map.of() with no arguments also returns an empty unmodifiable map but is a different implementation. Collections.emptyMap() is a singleton (all calls return the same instance), which is memory efficient. Map.of() may or may not be a singleton depending on JDK implementation. Use Collections.emptyMap() as a return value for the default/null-object pattern; use Map.of(k, v) for small constant maps in tests or configuration.

Q104. How does Caffeine cache differ from a manually implemented LinkedHashMap LRU cache?

A: Caffeine is a high-performance caching library using the Window TinyLFU admission policy which is more accurate than pure LRU for frequency-skewed workloads. Key advantages over LinkedHashMap LRU: (1) thread-safe with near-ConcurrentHashMap performance (uses striped ring buffers to batch recency updates without locking); (2) automatic expiration (expireAfterWrite, expireAfterAccess, variable expiry); (3) automatic loading via CacheLoader; (4) statistics (hit rate, eviction count); (5) asynchronous loading (AsyncLoadingCache); (6) size-based or weight-based eviction. LinkedHashMap LRU is appropriate for simple single-threaded caches; Caffeine for production systems.

Q105. What is the internal structure difference between Java 7 and Java 8 HashMap?

A: Java 7 HashMap: bucket array of Entry<K,V>[] nodes forming simple singly-linked chains. Resize reverses chain order (LIFO insertion). No treeification. Worst-case O(n) per operation. Susceptible to infinite loop in concurrent resize. Java 8 HashMap: bucket array of Node<K,V>[]. Resize uses lo/hi list splitting (preserves insertion order, avoids circular links). Treeification to Red-Black tree at chain length 8 (capacity >= 64). Worst-case O(log n) per operation. New Node types: Node (list node), TreeNode (RB-tree node, extends LinkedHashMap.Entry). The hash spread function (XOR high bits) added. Java 8 is significantly more robust.

No comments
Leave a Comment