Interview Prep Zoneby CuriouserLabs

Question 2 of 5 · senior level

Explain the equals/hashCode contract. What breaks when it's violated, and how do records help?

🎤 Say this first

Contract: equal objects must have equal hash codes (the reverse isn't required); equals must be reflexive, symmetric, transitive, consistent, and false vs null. Violate it and hash-based collections silently corrupt: duplicates in HashSet, unfindable HashMap entries, broken caching. Records generate correct, component-based equals/hashCode/toString, eliminating the whole bug class for value types.

The full picture

The subtle symmetry trap with inheritance
class Point { int x, y;  /* equals compares x,y */ }
class ColorPoint extends Point { Color c; /* equals also compares c */ }

Point p = new Point(1, 2);
ColorPoint cp = new ColorPoint(1, 2, RED);
p.equals(cp);  // true  (Point ignores color)
cp.equals(p);  // false (ColorPoint checks color) -> SYMMETRY BROKEN

// There is NO way to extend an instantiable class with a new value
// component while preserving the contract (Effective Java, Item 10).
// Answer: prefer composition — or model values as records (final).
  • Use getClass() equality or make the class final/a record; instanceof-based equals is only safe when subclasses add no state.
  • Always base hashCode on exactly the fields used in equalsObjects.hash(...) or record generation.
  • For entities with DB identity, equality by business key or id (careful: id is null before persist) — never by mutable state.

🔄 Likely follow-up questions

  • Why must you override hashCode when overriding equals — show a concrete failure.
  • How do you write equals for a JPA entity given lazy proxies? (instanceof, not getClass)
  • What does compareTo being 'consistent with equals' mean for TreeSet?