Walk through exactly how HashMap uses hashCode() and equals() during a get(), and pinpoint where a broken hashCode causes the lookup to fail.
answer
- get = hash to one bucket, then equals within that bucket
- index = (n-1) & spread(hashCode)
- equals never runs on entries in other buckets
- Wrong hash => wrong bucket => null on a present key
- Constant hashCode is correct but O(n) slow; inconsistent hashCode is incorrect
basics
~20 sHashMap first calls hashCode() on the key to choose a bucket (an array slot), then walks the entries in that bucket calling equals() to find the exact key. If hashCode() is wrong, it picks the wrong bucket, so the right entry is never visited and equals() never runs.
solid answer
~50 sOn get(key), HashMap computes key.hashCode(), spreads/mixes those bits, and maps the result to a bucket index via (n-1) & hash, where n is the table capacity. It then traverses the entries chained in that single bucket — comparing the stored key to the query key with equals() (after a fast == and hash-equality short-circuit). The first equals() match returns its value; if the bucket is exhausted, it returns null. The hashCode() result therefore selects *which* bucket is searched, and equals() selects *which entry within it*. If a key's hashCode() is inconsistent with equals() — two equal keys hashing differently — get() lands in a bucket that doesn't contain the stored entry, scans it fruitlessly, and returns null even though an equal key is present. The same two-step probe drives put() (duplicate detection) and remove(), so all three silently fail. The fix is to derive hashCode() from the exact fields equals() uses.
go deeper
Knows get() uses hashCode then equals, and that a bad hashCode means the key can't be found. May not know the index formula.
Can narrate the full probe (hash -> spread -> (n-1)&hash -> scan one bucket -> equals) and locate exactly where a contract-violating hashCode diverts the lookup.
Distinguishes the two failure modes (inconsistent hash = incorrect; constant hash = correct-but-slow), mentions the cached-hash short-circuit and treeification, and ties it to put/remove too.
Reasons about hash distribution quality, collision/treeification thresholds, load factor and resizing implications, and the consequences for latency tails and DoS-style collision attacks at scale.
## What a HashMap is A `HashMap` stores key→value pairs and promises *average* O(1) `get`/`put`. It achieves this with a **hash table**: an internal **array** (call its length `n`, the *capacity*) whose slots are called **buckets**. Each bucket holds the entries (key+value) that "belong" there — in modern OpenJDK a bucket is a linked list of `Node`s (and converts to a balanced tree once it grows past ~8 entries, but the list mental model is enough here). ## Step-by-step: `map.get(key)` 1. **Compute the raw hash.** Call `key.hashCode()`. This returns an `int`. 2. **Spread the bits.** HashMap mixes the hash with `h ^ (h >>> 16)` so that high-order bits also influence the low-order bucket index. (This is an implementation detail, not part of your contract — but it explains why even a poor hashCode still distributes somewhat.) 3. **Pick the bucket index.** Capacity `n` is always a power of two, so HashMap maps the spread hash to a slot with `index = (n - 1) & hash`. That `&` is a fast equivalent of `hash mod n`. The result is **one** bucket — the only bucket that will be searched. 4. **Search that one bucket.** Walk its entries. For each stored node, HashMap does a cheap check first: compare the **stored hash** to the query hash, and try `==` (same reference). Only if the hashes match does it call `storedKey.equals(queryKey)`. The first node whose key `equals()` the query key wins, and its value is returned. 5. **Miss.** If the bucket runs out with no `equals()` match, `get` returns `null`. ## The crucial division of labor - **`hashCode()` decides *which bucket* is searched** (steps 1–3). - **`equals()` decides *which entry inside that bucket* is the answer** (step 4). Because step 4 only ever examines **one** bucket, `equals()` is **never** called on entries living in *other* buckets. That is the entire performance trick — and the entire trap. ## Where a broken hashCode kills the lookup Suppose `equals()` says `Key("A")` and another `Key("A")` are equal, but `hashCode()` was left as the inherited identity hash, so the two instances return different ints. You do `map.put(new Key("A"), 1)` then `map.get(new Key("A"))`: - `put` hashed the first instance to bucket **5** (say) and stored it there. - `get` hashes the second instance — a *different* int — to bucket **11**. - Step 4 scans bucket **11**, which does not contain the stored entry. `equals()` is never run against the entry in bucket 5. - `get` returns **null**, even though a key that `.equals()` the query is sitting in the map. No exception, no warning — just a wrong answer. `put` uses the same probe to detect duplicates, so you can even insert what should be the "same" key twice; and `remove` can't find the entry to delete it. ## The narrow vs. wide failure modes - **hashCode too *fine* (equal objects hash differently):** the contract-violating case above — entries become unreachable. - **hashCode too *coarse* (everything returns the same constant, e.g. `return 1;`):** this does **not** violate the contract, so lookups still *work* — but every key piles into one bucket, degrading get/put to O(n) (or O(log n) after treeification). Correct, just slow. ## The fix Derive `hashCode()` from **exactly** the fields `equals()` compares: `return Objects.hash(field1, field2);`. Then equal keys always produce equal hashes, land in the same bucket, and step 4 finds them.
- If a class returns a constant hashCode like 'return 42;', does get() still work correctly?Yes — correctness is preserved because the contract (equal => same hash) holds; equal keys still collide in the same bucket and equals() finds them. But every key lands in one bucket, so get/put degrade from O(1) to O(n) (O(log n) after treeification in modern HashMap). It's a performance bug, not a correctness bug.
- Why does HashMap compare stored hashes before calling equals() inside the bucket?It's a cheap early-out: comparing two cached ints is far faster than a full equals(), and entries in the same bucket can still have different full hashes (because the bucket index only uses some bits). If the stored hash differs, equals() can be skipped entirely, which speeds up collision chains.
saying these in an interview costs you the question
- Saying HashMap calls equals() on every entry in the whole map
- Thinking a constant hashCode (return 1) is a *correctness* bug rather than a performance bug
- Claiming the lookup scans multiple buckets when the first misses
- Ignoring that put() and remove() fail the same way as get()
- Asserting hashCode() must be unique per object