Java HashMap Interview Questions: Collisions, Mutable Keys, and Concurrency

Prepare for Java HashMap interviews with collision examples, mutable-key failures, sizing trade-offs, iterator limits, and atomic ConcurrentHashMap updates.

Author: PracHub

Published: 9/15/2026

Java HashMap Interview Questions: Collisions, Mutable Keys, and Concurrency

September 15, 2026

Quick Overview

Prepare for Java HashMap interviews with collision examples, mutable-key failures, sizing trade-offs, iterator limits, and atomic ConcurrentHashMap updates.

Backend EngineerFree

A Java map contains one entry, and iteration still shows its key. Yet map.get(key) returns null, even though the caller holds the exact object originally inserted. Another program replaces HashMap with ConcurrentHashMap and still loses increments. Neither problem is solved by memorizing the treeification threshold.

Strong Java HashMap interview answers separate key contracts, implementation choices, and application invariants. Hashing determines where to look; equality determines what matches; concurrency policy determines which combinations of operations are safe. Improving one layer does not automatically repair another.

Evidence boundary: Official contracts below use Java SE 26. Implementation notes were checked against OpenJDK source, including the source bundled with local OpenJDK 26.0.2.1. Original Java exercises ran on that runtime; their observations are not portable promises for invalid key usage or performance benchmarks. No candidate reports or employer question-frequency claims are used.

Start with Explain hash collisions and Java HashMap complexity, then explain a failure that a complexity answer alone would miss.

Java HashMap interview preparation with indexed drawers and key cards on an ivory and green desk

What do hashCode and equals each guarantee?

Official fact: The Object contract requires equal objects to have equal hash codes. Unequal objects may share a hash code. Hash consistency depends on the information used in equality comparisons remaining unchanged. Object equality and hashing.

A collision is therefore not evidence of incorrect equality. Two customer identifiers can produce the same hash while remaining different keys. Conversely, two different object instances representing the same logical identifier should retrieve the same mapping when their equality and hashing contracts support that identity.

In the original local collision exercise, twenty unequal keys deliberately returned the same hash code. Every mapping remained retrievable. Inserting another key equal to identifier 3 replaced that identifier’s value without increasing the map’s size. This distinguishes a collision from replacement of an equal key.

Design inference: Define logical identity before generating methods in an IDE. Decide whether a key includes tenant, account, version, or another scope. A missing tenant component can create incorrect equality even with excellent hash distribution. Test equal-but-distinct instances, unequal colliding instances, and the fields that must distinguish business entities.

Avoid using a hash value itself as a unique business identifier. The permission to collide means that a hash alone cannot prove two records are the same.

How can a mutable key become effectively unreachable?

Official fact: The Map contract leaves behavior unspecified when a key changes in a way that affects equality while stored in the map. An unmodifiable map does not make its contained key objects deeply immutable. Map key restrictions.

Here is the original deliberately unsafe key class used in the local test:

static final class Key {
    int id;
    Key(int id) { this.id = id; }
    public int hashCode() { return id; }
    public boolean equals(Object other) {
        return other instanceof Key k && id == k.id;
    }
}

After inserting a key with id = 1, the test changed its identifier to 2:

HashMap<Key, String> map = new HashMap<>();
Key key = new Key(1);
map.put(key, "saved");
key.id = 2;                 // Violates the key contract
System.out.println(map.get(key));
System.out.println(map.size());

The observed output was null followed by 1. Iteration still returned the same key object. The entry had not been intentionally removed; the lookup no longer followed the assumptions under which it had been placed.

Implementation observation: OpenJDK stores a hash in each node and computes a lookup hash from the supplied key. Changing the key can break that relationship before an identity comparison could help. This explains the local result; it does not turn invalid mutation into a supported behavior. OpenJDK HashMap source.

MomentObserved state
Insert identifier 1Lookup succeeds
Change identifier to 2Key contract violated
Lookup same objectReturns null locally
Inspect sizeStill one mapping

Design inference: Prefer a stable immutable identifier as the key and keep mutable profile data in the value. A record with a mutable list component is not automatically a deeply immutable key: changing the list can still change equality-related state. Review the objects reachable from the key, not just whether its fields are final.

If identity must change, remove the entry while the old identity is still valid, then construct or update the key and reinsert under a defined collision policy. The local remove-before-mutate example succeeded. Once corruption already exists, do not casually mutate the object again and assume every affected structure has been repaired.

A stored key keeps its old lookup placement while a changed identifier sends a new lookup elsewhere

Are HashMap operations always constant time?

Official fact: HashMap describes constant-time basic operations when hashes distribute entries appropriately. Iteration depends on capacity plus size. It permits a null key and null values, does not guarantee iteration order, and is not synchronized. HashMap API.

Interview inference: State assumptions before saying O(1). A lookup includes hashing and equality work; if either traverses a large object, that cost does not disappear inside a bucket. An insertion can also trigger resizing. Average behavior under a useful distribution is different from promising that each individual operation has a fixed latency.

Separate three experiments: a well-distributed lookup workload, a collision-heavy workload, and growth that crosses resize boundaries. Keep key construction outside the timed region if you intend to measure map access, and include it deliberately if key creation is part of the real workload.

The twenty-key collision exercise verifies correctness, not a complexity curve. It cannot establish a production latency claim from one small successful run. A convincing answer identifies the workload and measurement method needed to test the performance hypothesis.

Also avoid relying on the iteration order you happened to observe with a few integer keys. If output order is part of the API contract, choose and test a data structure that promises the required ordering.

What should you say about treeification thresholds?

Implementation detail: The inspected OpenJDK source defines a treeification threshold of 8 and a minimum table capacity of 64. The treeification path may resize a smaller table instead. These constants describe implementation policy, not the general Map interface. OpenJDK HashMap implementation.

Do not reduce the answer to “the eighth collision always creates a tree.” Explain whether you are counting existing nodes or the new insertion, which insertion path is involved, and whether the capacity condition has been met. If exact transition behavior matters, inspect the target version’s code and build a controlled experiment for that path.

Design inference: A tree-shaped bucket is an optimization for problematic collision patterns, not permission to choose unstable or deliberately poor keys. It cannot restore an equality contract broken after insertion, and it does not turn every user-supplied hashing or comparison function into constant-time work.

When asked about worst-case behavior, qualify the key properties and implementation. Do not promise universal logarithmic lookup merely because a bucket uses tree nodes. The way equal hashes and comparable keys are resolved matters. Treat “there is a tree” as the start of the explanation, not its conclusion.

How do capacity and load factor change the trade-off?

Official fact: Capacity and load factor affect growth; the default load factor is 0.75. The current API includes HashMap.newHashMap(expectedMappings) for an expected mapping count. Constructor capacity and expected number of mappings are different concepts. HashMap sizing.

Consider an original planning exercise: a map is expected to hold 1,000 entries. Dividing 1,000 by 0.75 gives about 1,333.33 buckets before accounting for implementation rounding. This illustrates why asking for an initial capacity of 1,000 does not mean “reserve for exactly 1,000 entries without growth.”

Design inference: Use a version-appropriate sizing API or calculation, then validate memory and growth behavior for the real entry count. An enormous initial capacity can waste memory and increase iteration work when most buckets remain empty. “Avoid every resize” is not a complete objective if the map is usually small.

Ask whether the workload mostly performs lookups, frequently iterates, or repeatedly grows and discards maps. Measure allocation and pause behavior alongside operation latency when resizing is the suspected bottleneck. Do not claim an exact byte cost from entry count alone without checking the runtime’s object layout and retained objects.

Why can ConcurrentHashMap still lose an increment?

The original concurrency test used a ConcurrentHashMap<String, Integer> initialized to zero. Two workers each read the old count, waited at a barrier until both reads finished, and then wrote old count plus one. Each individual map call was supported; the intended increment was split across two calls.

Worker AWorker B
Reads 0Reads 0
Computes 1Computes 1
Writes 1Writes 1

The final count was 1, not 2. This is a controlled lost-update example, not an attempt to infer behavior from unsafe concurrent mutation of an ordinary HashMap.

Official fact: ConcurrentHashMap supplies atomic operations such as putIfAbsent, compute, and merge. Its computation callbacks should remain short, with documented restrictions on modifying the map during computation. ConcurrentHashMap atomic methods.

The repaired local exercise used counts.merge("x", 1, Integer::sum). Two workers performing 1,000 increments each produced 2,000. The result validates that example’s count invariant; it is not a benchmark comparing concurrent-map implementations.

Design inference: Name the invariant before choosing the operation. Updating one counter differs from transferring a balance between two keys or keeping a map and eviction list consistent. Per-key atomicity is not a transaction across every related object. Broader invariants may need coordinated locking, immutable state replacement, or another ownership model.

Thread-safe publication of a value also does not make subsequent unsynchronized mutation of that value safe. A concurrent map containing ordinary mutable lists still requires a policy for those lists.

Is fail-fast iteration a concurrency safety mechanism?

Official fact: HashMap’s fail-fast detection is best effort and must not be used as a correctness guarantee. ConcurrentModificationException is intended to reveal bugs, not provide synchronization. HashMap iterator contract.

In the local single-thread test, an iterator was created, a new entry was inserted outside that iterator, and the next iterator operation threw the exception. This demonstrates that the exception does not require two threads. Its absence would not prove a concurrent program safe.

Official fact: A Collections.synchronizedMap wrapper requires callers to synchronize on the returned map when traversing its collection views. Collections synchronized-map guidance.

Design inference: Decide whether a reader needs a stable snapshot or can accept a view changing during traversal. Include every writer in the chosen locking or ownership policy. Locking only the loop while another code path bypasses the same lock does not establish the intended consistency.

What does a null result tell you?

The original null-value test stored "known" -> null. Both get("known") and get("missing") returned null, but containsKey distinguished the stored mapping from absence. The output alone was ambiguous because this map intentionally permitted null values.

Design inference: Define absence explicitly in the application API. If null carries a business meaning, test it separately from a missing mapping. Under concurrency, a separate existence check followed by a lookup is another multi-step observation; explain whether intervening changes are acceptable.

A debugging answer should therefore ask about null policy, key stability, and synchronization before interpreting one null result as a hash collision or lost entry.

Which PracHub questions make useful follow-ups?

These verified records practice the relevant mechanisms. They are not predictions of which questions a particular interviewer will reuse.

Return to Explain hash collisions and Java HashMap complexity and explain the mutable-key trace before discussing tree nodes. Then show why a concurrent collection still needs an atomic increment operation.

Sources and Further Reading


Comments (0)