Why must you override hashCode() whenever you override equals() in Java?
answer
- Two-step lookup: hashCode picks the bucket, equals confirms the entry
- Equal objects MUST share a hashCode (contract); unequal MAY collide
- Default hashCode is identity-based, so override-equals-only breaks it
- Wrong bucket => equals never called => object unretrievable
- Override both, from the SAME fields
basics
~20 sBecause Java's hash-based collections (like HashMap and HashSet) first use hashCode() to find the right bucket, then equals() to compare. If equal objects return different hash codes, they land in different buckets and the collection never finds the match.
solid answer
~40 sThere's a contract linking the two methods: if two objects are equal by equals(), they must return the same hashCode(). Override equals() alone and you break it, because the default Object.hashCode() returns a value tied to each object's identity, so two logically-equal objects get different hash codes. Hash-based collections (HashMap, HashSet) locate entries in two steps: compute hashCode() to pick a bucket, then walk that bucket calling equals(). If two equal objects hash differently, they go to different buckets, so a lookup that hashes one will never even visit the bucket holding the other, and equals() is never consulted. The result: contains() returns false for an object that is equal to a stored one, and a HashSet can hold two equal elements. Always override both together, using the same fields in each.
go deeper
Knows you must override both together and that HashMap/HashSet misbehave otherwise. Can state the rule even without explaining bucket internals.
Can explain the two-step hash-then-equals lookup and why a wrong hashCode sends the lookup to the wrong bucket so equals is never called.
Articulates the full contract (equal => same hash; unequal MAY collide), the default identity-hash behavior, and demonstrates a correct paired implementation using Objects.hash with matching fields.
Discusses downstream consequences (silent data-integrity bugs, mutable-key hazards, distribution quality of hash functions, IDE/record/Lombok generation as the standard mitigation) and sets team conventions to prevent the bug.
## The two methods and what they mean Every Java object inherits two methods from `java.lang.Object`: - **`equals(Object o)`** — answers "is this object *logically the same* as that one?" The default implementation is **reference identity**: `a.equals(b)` is true only when `a` and `b` are literally the *same* object in memory (the same as `a == b`). You override it when two *separate* objects should count as equal — e.g. two `Point(1,2)` instances. - **`hashCode()`** — returns an `int` that acts as a fast, lossy "summary" of the object. The default returns a number derived from the object's identity (historically its memory address), so two distinct objects almost always get *different* hash codes. ## The contract that binds them The Javadoc of `Object` states a **contract**: > If two objects are equal according to `equals()`, then calling `hashCode()` on each **must** produce the **same** integer. (The reverse is NOT required: two *unequal* objects MAY share a hash code — that's a *collision*, and it's allowed.) When you override `equals()` to compare by fields but leave `hashCode()` inherited, you break this contract: two objects that are now `equals()`-equal can still return the two *different* identity-based hash codes they inherited. The compiler does not catch this — it's a silent logic bug. ## Why hash-based collections care A **hash table** (the engine inside `HashMap` and `HashSet`) stores entries in an array of *buckets*. To store or find a key it does **two steps**: 1. **Hash step:** call `key.hashCode()`, reduce it (e.g. modulo the array length) to pick **one bucket**. 2. **Equality step:** scan only the entries *in that bucket*, calling `equals()` on each, to find the exact match. This two-step design is what makes lookups average O(1) instead of O(n): you never scan the whole table, only one small bucket. Now suppose you stored `new Point(1,2)` and later call `set.contains(new Point(1,2))`. The two Points are `equals()`-equal but, with the broken setup, have **different** hash codes. Step 1 sends the lookup to a *different bucket* than where the stored Point lives. Step 2 scans that (wrong, probably empty) bucket, never finds the entry, and `equals()` is **never even called** on the stored Point. So: - `contains()` / `get()` return **false / null** for an object that *is* equal to a stored one — the object is effectively **unretrievable**. - `HashSet` can end up holding **two objects that are equal**, because `add()` uses the same broken lookup to decide "is this a duplicate?" and concludes it isn't. ## The fix Override **both** methods, **using the same set of fields** in each. If `equals()` compares `x` and `y`, then `hashCode()` must be computed from `x` and `y` too (e.g. `return Objects.hash(x, y);`). Then equal objects always hash identically, the lookup reaches the right bucket, and the collection behaves correctly. ## Key intuition to keep `hashCode()` decides *where to look*; `equals()` decides *which one it is*. Get the "where" wrong and the "which" never runs.
- If two objects have the same hashCode, does that guarantee they are equal?No. Equal objects must share a hash code, but the reverse is not required — different objects may collide on the same hash code. The collection resolves collisions by then calling equals() within the bucket.
- Does overriding only hashCode() (and leaving equals() default) cause the same problem?It doesn't violate the contract the same way, but it's pointless: equals() still uses identity, so logically-equal objects are still treated as different. You'd never get duplicate-detection or retrieval to work. The contract is only broken when equals says 'equal' while hashCode disagrees.
saying these in an interview costs you the question
- Saying 'hashCode and equals are unrelated, override whichever you need'
- Claiming unequal objects must have different hash codes (collisions are allowed)
- Thinking the compiler or runtime warns you about the broken contract
- Believing a List or array lookup is affected (only hash-based collections are)
- Including different fields in hashCode than in equals