Articles
Java equals and hashCode Interview Questions: Collections, Inheritance, and Records
Prepare for Java equals and hashCode interview questions with collection contracts, inheritance failures, mutable keys, and tested record examples.
Java equals and hashCode interview questions test whether your definition of a value survives collection use, inheritance, and mutation. The central contract is directional: equal objects must have equal hash codes, but equal hash codes do not prove equality. You also need an equality relation that stays symmetric and transitive. A generated implementation can satisfy the syntax while still expressing the wrong domain identity.
Imagine two independently loaded order keys that should find the same cache entry. Then imagine changing one key after insertion, adding a subclass with an extra identity field, or replacing the class with a record containing an array. Each change tests a different assumption. For broader JVM and collection preparation, see Java Interview Questions for Software Engineers.
Evidence boundary: Official facts below come from Java API specifications. Candidate reports: no verified report establishes these exact exercises or an employer's scoring rules. Inference: the order-key scenarios, fixtures, and recommendations are original practice material. Examples use ordinary Java features available in Java 21; source links identify the Java SE 26 contract documentation consulted.
Which contracts must equals and hashCode satisfy?
Official fact: Object's API contract requires equals to be reflexive, symmetric, transitive, and consistent while the information used for comparison remains unchanged. A non-null object must compare unequal to null. Equal objects must return the same hash code; unequal objects may collide.
For a proposed OrderKey, turn those requirements into observable checks rather than a memorized list.
- Reflexive: a valid key equals itself.
- Symmetric: comparing a base key to another representation gives the same answer in reverse.
- Transitive: if the first equals the second and the second equals the third, the first equals the third.
- Consistent: repeated calls agree while equality-relevant state is unchanged.
- Null handling: a valid key does not equal null.
- Hash agreement: independently constructed equal keys have equal hash codes.
Reference identity and logical equality answer different questions. a == b on object references asks whether they refer to the same object. a.equals(b) uses the selected class's equality behavior. When debugging a cache, state whether two representations of the same business key should be interchangeable, rather than assuming one operation is always superior.
A constant hash code can meet the equal-implies-same-hash requirement while producing poor distribution. Conversely, a fast, well-distributed hash based on fields excluded from equality can violate correctness. Optimize distribution only after the equality contract is coherent.
How does that contract affect maps and sets?
Start with a small immutable identity. This record makes both tenant and order number part of equality. Two tenants may reuse an order number, so omitting the tenant would merge distinct domain keys.
record OrderKey(String tenant, long orderNumber) {}
var first = new OrderKey("north", 42);
var same = new OrderKey("north", 42);
var other = new OrderKey("south", 42);
var entries = new java.util.HashMap<OrderKey, String>();
entries.put(first, "pending");
assert entries.get(same).equals("pending");
assert entries.get(other) == null;
assert first.hashCode() == same.hashCode();
The key decision is which fields define identity. A customer's display name may change without changing the customer. A versioned document key might deliberately include its revision. Explain that policy first, then implement matching equality and hashing. This is a domain model decision that the collection cannot make for you.
Do not present hashing as a unique fingerprint. A hash-based collection must distinguish unequal keys that collide. Overriding equals without a compatible hashCode can make an equal fresh key fail to retrieve an inserted entry. Calling Objects.equals at the call site adds null-safe dispatch; it cannot repair the class's broken equivalence relation.
Also identify the collection. An identity-based map intentionally follows reference identity, while a sorted set uses its ordering relation. “All collections call equals in the same way” is not an accurate explanation of Java's collection APIs.
Why are mutable keys dangerous?
Official fact: Map's documentation warns that behavior is unspecified when a key is changed in a way that affects equality while it is in the map. That warning is stronger and more portable than promising a particular failed lookup for every implementation.
Our diagnostic scenario is a key whose hash includes an editable account code. At insertion, the collection organizes it using the old value. After the account code changes, a lookup follows the new equality and hashing behavior, even though the stored entry has not been deliberately removed and reinserted. A local implementation may now fail to find the entry; another observation does not make the design valid.
The useful fix is a stable key, not a synchronized wrapper around the map. Concurrency control does not restore an equality invariant that application code intentionally changed. Separate immutable identity from mutable attributes, or create a new key when the identity genuinely changes.
If a workflow must rename a key, make the operation explicit: locate and remove the old mapping while the old identity is intact, validate conflicts, and insert the new key according to an atomicity policy. Mutating an object already embedded in several maps and sets is harder to reason about because every container may depend on its former identity.
In an interview, test lookup using a distinct equal object as well as the same reference. Testing only map.get(original) can hide defects in the intended value semantics. Also test duplicate insertion, removal through an equal key, and unequal keys with deliberately colliding hashes.
How can inheritance break symmetry?
Suppose a base class compares only an integer ID and accepts any subclass. A subclass then insists that an extra scope match. The following deliberately broken example exposes the disagreement.
class BasicKey {
final int id;
BasicKey(int id) { this.id = id; }
@Override public boolean equals(Object o) {
return o instanceof BasicKey k && id == k.id;
}
@Override public int hashCode() { return id; }
}
class ScopedKey extends BasicKey {
final String scope;
ScopedKey(int id, String scope) {
super(id); this.scope = scope;
}
@Override public boolean equals(Object o) {
return o instanceof ScopedKey k
&& id == k.id && scope.equals(k.scope);
}
@Override public int hashCode() { return id; }
}
var basic = new BasicKey(7);
var scoped = new ScopedKey(7, "internal");
assert basic.equals(scoped);
assert !scoped.equals(basic);
Both objects happen to use the same hash here, so the failure is specifically equality symmetry. Improving the subclass's hash distribution would not fix it. This is why a collection bug can begin in the domain hierarchy rather than in the map implementation.
Possible repairs express different policies. A final value class can define one closed equality domain. Exact runtime-class checks can keep classes distinct, if that matches the intended semantics. Composition can represent a scoped key as a separate value containing a base identifier. A carefully designed extensible protocol is possible, but every participating subclass must cooperate.
Do not prescribe getClass() or instanceof without stating the policy. A base class that includes every subclass by identity alone may be coherent if subclasses never add equality-relevant state. The problem appears when two classes disagree about what counts as equal. Adding another field is not a harmless implementation detail once objects are used as keys.
What do records solve, and what do they leave open?
Official fact: Record's API specification describes generated equality using the same record class and corresponding components, with component-appropriate comparison rules. Records are implicitly final. Their fields hold final component references, which does not recursively freeze the referenced objects.
An array component makes that distinction visible:
record Batch(int[] values) {}
int[] source = {1, 2};
var a = new Batch(source);
var b = new Batch(new int[] {1, 2});
var c = new Batch(source);
assert !a.equals(b);
assert a.equals(c);
source[0] = 9;
assert a.values()[0] == 9;
Two different arrays with identical elements are not equal under ordinary array object equality. The generated record methods do not silently replace that rule with element-wise comparison. Mutating the shared array changes what the accessor reveals, even though the record's reference field is final.
If array contents should define value identity, provide compatible content equality and content hashing, and control mutation through defensive copying on construction and access. Official fact: Arrays provides element-wise equality and hash helpers, including separate deep operations for nested arrays. Select the operations that match the actual component shape.
Another option is an immutable element sequence. Our next example makes a structural snapshot of an incoming string list. It deliberately rejects null components and null list elements through List.copyOf.
record Labels(java.util.List<String> names) {
Labels {
names = java.util.List.copyOf(names);
}
}
var input = new java.util.ArrayList<>(java.util.List.of("new"));
var labels = new Labels(input);
input.add("changed");
assert labels.equals(new Labels(java.util.List.of("new")));
assert labels.names().size() == 1;
Official fact: List.copyOf produces an unmodifiable list with the documented null restrictions. It does not deep-copy mutable elements. Strings make our example straightforward; a list of editable objects would need another policy.
Do not assert an exact generated record hash formula. The useful promise is consistency with record equality, not a particular numeric hash that every runtime must produce. Tests that pin arbitrary generated hash numbers can be more brittle than tests of the contract.
What if a TreeSet disagrees with a HashSet?
Consider a comparator that orders user records only by case-insensitive display name, while record equality also includes a unique ID. Two records with different IDs can compare as zero. A TreeSet may therefore treat them as the same ordered element even though their equals result is false.
Official fact: TreeSet's documentation explains that ordering must be consistent with equality to satisfy the general Set contract. Its comparison-based behavior is defined even when that consistency is absent; the mismatch is a modeling issue to explain, not evidence that hashing malfunctioned.
Repair the comparator with a tie-breaker if the set should retain both values, or define equality consistently with the intended ordering if they truly represent the same value. Do not automatically change hashCode: the sorted set is not using it to make that decision.
Practice the contract through concrete failures
Our local verification executes the examples with assertions enabled, then checks equal-key replacement and removal, a deliberate unequal-key collision, the asymmetric hierarchy, record arrays, a copied list, and comparator-based deduplication. The mutable-key case is recorded as an implementation observation, not a portable collection guarantee.
| Practice question | Contract to explain |
|---|---|
| Java and Object-Oriented Design Fundamentals | Define a coherent equality domain before implementing methods |
| Explain hash collisions and Java HashMap complexity | Separate collisions, correctness, and performance |
| Java Core Deep Dive: Concurrent Collections and Java 8 Features | Explain why concurrency tools do not repair mutable identity |
| Explain core Java and Spring fundamentals | Connect language contracts to application objects |
| Explain Java follow-ups after solving coding problem | Defend collection choices and edge cases after implementation |
Start with Java and Object-Oriented Design Fundamentals. Bring three objects, not just two: test both comparison directions and a transitivity chain. Then place equal and unequal examples into the collection your design actually uses.