Beyond correctness, what makes a hashCode() implementation 'good', and why does it matter for HashMap performance?
answer
- Correct != good: return 0 is correct but awful
- Good spread -> O(1); clustering -> O(n)
- 31*result + field, order-sensitive, odd prime
- HashMap spreads high bits down (h ^ h>>>16)
- Java 8 treeifies long buckets to O(log n)
- Predictable hash -> hash-flooding DoS
basics
~20 sA correct hashCode just needs equal objects to share a code. A good one also spreads unequal objects across many different codes, so HashMap buckets stay small and lookups stay fast. A poor spread piles items into few buckets and slows lookups.
solid answer
~50 sCorrectness and quality are separate. The contract only forces equal objects to share a code; a hashCode that returns a constant is technically *correct* yet terrible — every entry lands in one bucket, so HashMap degenerates toward O(n) (or O(log n) once Java 8+ treeifies long buckets). A *good* hashCode distributes unequal objects uniformly across the int range and uses all the equals-relevant fields, so HashMap's bucket mapping spreads entries evenly and lookups stay near O(1). The classic idiom multiplies an accumulator by an odd prime (31) per field — `result = 31*result + fieldHash` — which makes the result order-sensitive and reduces collisions; `Objects.hash(...)` does this for you. HashMap further applies an internal spreading function to mix high bits down. Distribution quality also has a security dimension: predictable, easily-collided hashes enable hash-flooding denial-of-service, which is why some keyed structures randomize their hashing.
code
java · 16 lines// Bad-but-legal: contract-compliant, performance disaster
@Override public int hashCode() { return 1; } // all keys -> one bucket
// Classic good idiom (order-sensitive, odd-prime fold)
@Override public int hashCode() {
int result = 17;
result = 31 * result + Integer.hashCode(x);
result = 31 * result + Integer.hashCode(y);
result = 31 * result + (name == null ? 0 : name.hashCode());
return result;
}
// Idiomatic equivalent
@Override public int hashCode() {
return java.util.Objects.hash(x, y, name);
}go deeper
Knows a constant hashCode is legal but makes HashMap slow, and that a good hashCode spreads objects out.
Explains the O(1) vs O(n) bucket effect, uses Objects.hash or the 31-multiplier idiom, and includes all equals fields.
Connects distribution to HashMap internals (power-of-two table, bitmask index, the high-bit spread function, Java 8 treeification) and reasons about order-sensitivity and field selection.
Adds the adversarial view — hash-flooding DoS from predictable hashes, randomized hashing trade-offs — and weighs caching hashCode for hot immutable keys against memory and code complexity.
## Correctness vs. quality The hashCode **contract** is a *correctness* floor: equal objects share a code, codes are consistent within a run, collisions are allowed. **Quality** is a separate, performance concern: *how well* the codes spread unequal objects. A method like `return 0;` (or `return 42;`) is fully **contract-compliant** yet **pathologically bad**, because it forces every key into a single bucket. ## How HashMap turns a code into a bucket A `HashMap` keeps an array (the *table*) whose length is a power of two. For a key it computes `h = key.hashCode()`, applies an internal **spread** (`h ^ (h >>> 16)`, mixing the high 16 bits into the low 16), then takes `index = spread(h) & (table.length - 1)` — a fast bitmask that keeps only the low bits. **Implication:** only the *low* bits of your hashCode select the bucket initially, so a hashCode whose entropy lives only in high bits would collide heavily without that spread step — the JDK's `hash()` exists precisely to mitigate weak hashCodes. ## Why distribution governs speed - If codes are well spread, each bucket holds ~1 entry and `get`/`put` are **O(1)** average. - If many keys share a bucket, that bucket becomes a list that must be scanned with `equals`, pushing toward **O(n)**. - Since Java 8, once a single bucket exceeds a threshold (8 entries, table ≥ 64) it is **treeified** into a balanced (red-black) tree, capping that bucket's lookup at **O(log n)** — a safety net for bad hashCodes and hash-flooding, *not* a license to write poor ones (treeification requires the keys be `Comparable`, or it falls back). ## What a good implementation looks like 1. **Use all and only the equals-relevant fields.** Omitting a distinguishing field raises collisions; including a field equals ignores can violate the contract. 2. **Combine fields order-sensitively with an odd prime.** The textbook idiom: `int result = 17; result = 31*result + a; result = 31*result + b;`. 31 is odd and prime, the multiply is cheap (`31*x == (x<<5) - x`), and being order-sensitive distinguishes objects with the same fields in different roles (e.g. `(x=1,y=2)` vs `(x=2,y=1)`). 3. **In practice, call `Objects.hash(f1, f2, ...)`** (which boxes into an array and applies the same 31-based fold) for clarity, or let a **record** generate it. For a single field, delegate to that field's own `hashCode`. 4. **Consider caching for immutable types.** If hashCode is expensive and the object is immutable (e.g. `String` does this), compute it lazily once and store it. ## The collision allowance is not an excuse The contract *permits* collisions, but every avoidable collision costs lookup time. So 'collisions allowed' is a correctness statement, while 'minimize collisions' is the quality goal you optimize toward. ## Security: hash-flooding If an attacker can submit keys (e.g. HTTP parameters parsed into a `HashMap`) and your hashCode is predictable, they can craft thousands of distinct keys that all collide into one bucket, turning O(1) into O(n) and causing a **denial-of-service** (algorithmic complexity attack). Mitigations include treeified buckets (Java 8+) and, for some structures, randomized/seeded hashing. This is why a 'good' hashCode in adversarial contexts means both well-distributed *and* hard to predict.
- Why is 31 the conventional multiplier?It is an odd prime, which avoids information loss that even multipliers cause (they push bits toward zero), and JITs/compilers can compute 31*x as (x<<5)-x, making it cheap. Other odd primes work too; 31 is just the well-known convention from String and Effective Java.
- Does the Java 8 bucket treeification make a bad hashCode harmless?No. Treeification caps a single overloaded bucket at O(log n) and requires keys to be Comparable to be effective, but it is a safety net, not a substitute. A poorly distributed hashCode still wastes memory, triggers treeification overhead, and may not treeify if keys aren't Comparable.
Think of buckets as checkout lanes. A correct-but-bad hashCode sends every shopper to lane 1 (one giant queue). A good hashCode is a greeter who evenly directs shoppers across all lanes, keeping each line short — fast for everyone.
saying these in an interview costs you the question
- Treating 'collisions are allowed' as license to ignore distribution
- Returning a constant or a single mutable field's value
- Believing HashMap is always O(1) regardless of hashCode quality
- Thinking treeification removes the need for a good hashCode
- Ignoring the hash-flooding DoS angle for attacker-supplied keys