Question 11 · Section 10

What Happens If You Override hashCode() But Not equals()?

If you override only hashCode(), objects with the same hash land in the same bucket, but HashMap still considers them different — because equals() by default compares object ref...

Language versions: English Russian Ukrainian

🟢 Junior Level

If you override only hashCode(), objects with the same hash land in the same bucket, but HashMap still considers them different — because equals() by default compares object references (==).

What happens:

public class User {
    private Long id;

    @Override
    public int hashCode() { return id != null ? id.intValue() : 0; }
    // equals() NOT overridden — Object.equals() is used (reference comparison)
}

Map<User, String> map = new HashMap<>();
map.put(new User(1L), "Alice");  // hashCode=1, bucket #1 → equals not called (bucket empty) → OK
map.put(new User(1L), "Bob");    // hashCode=1, bucket #1 → equals(Alice, Bob) → false (Object.equals = reference comparison!) → new element
System.out.println(map.size()); // 2! Two "identical" keys

Main rule: If you override hashCode — override equals too!

🟡 Middle Level

Practical Consequences

1. Duplicates in HashMap:

  • Both User(id=1) land in the same bucket (hashCode = 100)
  • On equals() check, references are compared → different objects → different keys
  • HashMap allows both

2. Impossible data retrieval:

  • Put new User(1L) → try to get by new User(1L)
  • hashCode is correct (same bucket)
  • equals() returns false (different references)
  • Result: null

3. Increased memory consumption:

  • HashSet contains more elements than expected. With high data volume, this leads to increased memory consumption and potential OutOfMemoryError.

Comparison of Two Situations

Situation Where searched? Result
equals OK, hashCode NO Different buckets Not found (wrong place)
hashCode OK, equals NO Same bucket Not found (not recognized)

Performance Consequences

Same hashCode for all keys → all elements in one bucket → degradation to O(n)/O(log n). Plus pointless equals() calls.

🔴 Senior Level

Internal Mechanics

On map.get(new User(1L)):

// 1. Find bucket (correct): index = (n-1) & hash = 100
// 2. Iterate elements in bucket 100
// 3. For each: p.hash == hash (true) → p.key.equals(key)
// 4. Object.equals(): this == obj → false (different objects)
// 5. Return null

The element is physically in the correct bucket, but not recognized.

Memory Leak in Production

Set<Event> uniqueEvents = new HashSet<>();
// hashCode overridden by eventId, equals — not
for (Event e : stream) {
    uniqueEvents.add(e); // Every object is added!
}
// Result: OOM when processing a large stream

Comparison with Object.equals()

// Object.equals() — simply:
public boolean equals(Object obj) { return this == obj; }

This compares references, not content. Even two new User(1L) — different references → different objects.

Edge Cases

  1. Singleton-like objects: If the same instance is always used, the problem won’t manifest (but it’s a fragile solution)
  2. Deserialization: On deserialization, a new object is always created — the problem is guaranteed

Production Diagnostics

Signs of the problem:

  • Growing collection size without expected growth
  • containsKey() returns false for “existing” keys
  • Heap dump shows many “identical” objects in Map

Best Practices

  1. Always generate the pair — use IDE or Lombok @EqualsAndHashCode
  2. Records — automatically generate both methods
  3. Static analysis — SonarQube/SpotBugs finds this violation

🎯 Interview Cheat Sheet

Must know:

  • Without equals(), objects with the same hashCode land in the same bucket, but Object.equals = reference comparison (==)
  • HashMap allows duplicates: two new User(1L) = different keys
  • get() returns null — correct bucket, but equals doesn’t recognize the equal object
  • OOM when processing streams: HashSet adds every object instead of deduplicating
  • Object.equals() = just this == obj, reference comparison

Common follow-up questions:

  • How does it differ from “equals OK, hashCode NO”? — there, search is in the wrong bucket; here, in the correct one, but not recognized
  • When won’t the problem manifest? — if the same instance is always used (singleton)
  • Why OOM? — uniqueEvents.add() adds every object, Set grows infinitely
  • Does deserialization make it worse? — yes, always creates a new object — problem guaranteed

Red flags (DO NOT say):

  • “This is a less serious error than missing hashCode” — no, both are critical
  • “equals doesn’t need to be overridden for immutable objects” — no, even immutable objects are compared by reference
  • “This doesn’t occur in production” — it does, on deserialization

Related topics:

  • [[07. What is the equals() and hashCode() Contract]]
  • [[10. What Happens If You Override equals() But Not hashCode()]]