Sitemap
The Java Interview Playbook

Java Interview Answers I Wish I Knew Earlier

equals() & hashCode() methods in Java

9 min readDec 8, 2025

--

TL;DR

The methods equals() and hashCode() come from java.lang.Object and determine how objects are compared (and used in hash-based collections).

By default, equals() compares object references and hashCode() returns a number derived from the object’s identity ,i.e., an int hash code value.

In practice, most classes override them to define logical equality (same data) and consistent hashing.

Adhering to their contract is crucial aspect of working with collections like HashMap, HashSet, and other data structures that rely on hashing. Failing to maintain this contract can lead to bugs (e.g. lost map entries) and unpredictable behavior.

The equals() Method

The equals(Object obj) method, by default in Object class, uses reference equality to compare two objects.
But in most cases, it is overridden to perform logical equality by comparing relevant fields in that particular class. And hence we can say that equals method checks whether two objects are logically equal.

When overriding, one must follow the formal contract rules:

  • Reflexive: x.equals(x) must return true for any non-null x.
  • Symmetric: x.equals(y) should return the same as y.equals(x).
  • Transitive: if x.equals(y) and y.equals(z) then x.equals(z) must hold.
  • Consistent: repeated calls return the same result if objects don’t change.
  • Non-null: x.equals(null) must always return false.

These requirements are mandated by Java’s specification. For example, a typical override looks like:

@Override
public boolean equals(Object obj) {
// If both the object reference are same, no need to compare contents, and just return true
if (this == obj) return true;

// If the obj passed in is null, then to avoid NPE while comparison, it is better to just return false early on
if(obj == null) return false;

// If obj is not of type Person, then how can we compare it to Person, so return false, else it will give ClassCastException later on
if (!(obj instanceof Person)) return false;

// Type cast the obj to Person and do the required comparison
Person other = (Person) obj;

// Here name can be null, so Objects.equals is used
// It handles null values and delegates to the .equals() method for non-null comparisons
return Objects.equals(this.name, other.name)
&& this.age == other.age;
}

In this code, two Person instances are equal if their name and age fields match.
IDEs and libraries (e.g. Lombok’s @EqualsAndHashCode) often generate such methods automatically.

The hashCode() Method

The hashCode() method is present inside the Object class and hence every object can call this method and get a value sinceObject class is parent class of all the classes. Hence, this is valid even if the class does not implement the hashCode() method:

MyObj obj = new MyObj();
int hashValue = obj.hashCode();

The hashCode() method returns an int and is used by hash-based collections to place objects into buckets.

For each different object it will give a different hash code value. This hash code value is generated using the memory address of the object, and then performing some operations onto it.

But there is no implementation of hashCode() in Object class also, how so?
In Java’s Object class, the hashCode() method does have a body, but you don’t see it in the source code because it is a native method.

public native int hashCode();

Why does it not have a body?
Because hashCode() is implemented in native code (typically C/C++) inside the JVM, not in Java.
When a method is marked native, the implementation is provided by the JVM, not in the Java source.

Why did Java designers make hashCode() native?
Because the hashCode() requires the memory address of the object to perform some operations, and Java does not have access to this memory, which the JVM knows best.
Java code does not have direct access to raw memory addresses, so JVM must compute it.

When overriding, one must follow the formal contract rules:

  • Consistency: The hash code of an object must remain the same across multiple calls within the same execution if the object’s state (fields used in equals()) doesn’t change.
  • Equals-consistency: If x.equals(y) is true, then x.hashCode() == y.hashCode() must hold.
  • Collision allowed: It’s allowed (though not ideal) for unequal objects to have the same hash code (a collision).

equals() and hashCode()

While overriding equals() and hashCode() methods, any fields used in equals() should also be used to compute hashCode(). For example:

@Override
public int hashCode() {
return Objects.hash(name, age);
}

This uses the same fields (name, age) as the equals() example above, ensuring consistency.

Here’s how the equals() and hashCode() are related:
If two objects are equal according to equals() method, they’ll have the same hash code.
If two objects have same hash code, then it is not necessary that they will be equal according to equals() method. This can be considered as a hash-collision.
Analogy: Consider two photographs of Kapil Sharma. You can compare these 2 photos and say that his name is Kapil, meaning that you compared the two objects using .equals() . If this is true, both pictures are equal, then they must generate the same hash code, same name in this case.
But, if we provide same hash code to 2 different objects, say we give the name “Kapil” to 2 objects, then can we say with guarantee that both objects / photos will be same?
No right, as one picture can be of Kapil Sharma and another can be of Kapil Dev. So, same hash code does not guarantee objects to be equal, i.e., equals() method can give false in this case.

Too many collisions (different objects sharing a hash) can degrade performance, but correctness only demands the weaker rules.

No matter how many times you call the hashCode() method on an object, the value will be same.

The equals–hashCode Contract

The two methods are linked by a formal contract: “if two objects are equal according to equals(), they must have the same hashCode()”.
Violating this rule breaks the expectations of hash-based collections.

Objects which are .equals() MUST have the same .hashCode(). The converse is not required: unequal objects may (due to collision) or may not share a hash code.

If the contract is violated (for example, by overriding only equals() but not hashCode()), collections can misbehave.

Hence if we override equals(), we must also override hashCode().

The core takeaway is: always override both or neither.
For classes where logical equality is needed (e.g. value objects), implement both consistently; for purely identity-based classes (e.g. entities with unique IDs), it’s often fine to use the default implementations.

How Hash-Based Collections Use equals() and hashCode()

Collections like HashMap and HashSet rely on this contract. A HashMap stores entries in buckets determined by hash codes, then checks keys with equals(). For a key lookup map.get(k), the process is:

  1. Compute k.hashCode() and use it to find the bucket (array index).
  2. In that bucket, traverse any entries: for each key k1, check k == k1 or k.equals(k1).
  3. If a matching key is found, return its value; otherwise return null.

HashMap uses hashCode(), == and equals() for entry lookup in this sequence.
A collision (two keys with the same bucket index) means multiple entries are chained (as a linked list or tree).

Get Mayank Pant’s stories in your inbox

Join Medium for free to get updates from this writer.

When inserting (put()), if an existing key in the bucket is .equals() to the new key, its value is replaced; if not, a new entry is added.

For example, if three distinct keys have identical hashCode(), the map will link them in the same bucket. If keys K1, K2, K3 all map to the same bucket (they share the bucket index), but equals() is false for each pair, so all three coexist as separate entries in that bucket.

In a HashSet, the same logic applies (a HashSet is backed by a HashMap internally). An element is considered already present if an existing element in the set is equals() to it and has the same hash code. Thus, correct overrides ensure that collections correctly identify duplicates and retrieve elements as expected.

Consequences of Incorrect Overrides

Failing to override both methods correctly leads to subtle bugs:

  • Only equals() overridden, no custom hashCode(): Two logically equal objects may end up in different buckets. If you put a key into a HashMap with a custom equals() but default hashCode(), retrieving with an “equal” new key fails (returns null). The objects compare as equal but their hash codes differ, so the lookup goes to the wrong bucket and doesn’t find the entry.
  • Only hashCode() overridden, default equals(): Here two logically equal objects have the same bucket, but equals()(identity check) sees them unequal. The map treats them as distinct keys. Even if hash codes match, a lookup still fails if equals() isn’t also overridden.
  • Neither overridden (both default): Objects are only equal if they’re the same instance. This is rarely the desired “logical” equality, but it does not violate the equals–hashCode contract. It simply means each object is unique as far as collections are concerned.

If you have two objects which are .equals(), but have different hash codes, you lose! In practice, you might not notice a problem immediately, but stored data can become inaccessible or keyed incorrectly.

Overriding equals() and hashCode(): Examples

Below is an illustrative example. Consider a simple Person class:

public class Person {
private String name;
private int age;
public Person(String name, int age) { ... }
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Person)) return false;
Person other = (Person) obj;
return age == other.age
&& Objects.equals(name, other.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}

Here, equals() returns true when name and age match, and hashCode() uses the same fields.
If we did not override hashCode(), two Person("Alice",30) objects (equal by equals()) would likely have different default hashes (identity-based), breaking HashMap lookup.

Likewise, in hash-based collections:

Map<Person,String> map = new HashMap<>();
map.put(new Person("Alice", 30), "Engineer");
// Later…
String role = map.get(new Person("Alice", 30)); // returns "Engineer" only if equals/hashCode aligned

Without the proper overrides, role would be null even though logically the key exists.

Common Mistakes

Programmers often err in the following ways:

  • Overriding one and not the other: As stressed above, this breaks the contract.
  • Using mutable fields: Including fields that can change (like a dateModified or email) in equals()/hashCode() can break collections. For example, a Customer’s email is updated, changing its hashCode, so a previously stored key can’t be found. Use immutable fields in hashCode() or avoid mutable keys.
  • Violation of symmetry/transitivity: For instance, using class inheritance unwisely: if subclass equals() compares only superclass fields, symmetry or transitivity can break.
  • Inconsistent field use: Using different fields in equals() vs. hashCode() violates consistency. Both methods should be based on the same set of significant fields.
  • Not checking for null/class: A bad equals() might throw a ClassCastException or return true for null inputs. Always return false for null and compare classes (as in standard recipes).
  • Performance pitfalls: Including heavy computations or non-deterministic values (like current time) in hashCode()can degrade performance or violate consistency.

Each of these mistakes has bitten developers, often only revealed by failing tests or customer issues. Libraries like EqualsVerifier can help catch many of these errors.

Best Practices

  • Override both or neither: If logical equality matters, implement both equals() and hashCode() together. Otherwise, use the default implementations.
  • Use the same fields: Make sure that any field used in equals() also contributes to hashCode(). This keeps the contract intact.
  • Prefer immutable keys: Immutable objects (like String, Integer, or custom objects with final fields) make excellent hash keys because their hash codes don’t change. If a class is mutable, be very cautious about using it as a key or storing it in sets.
  • Use helper libraries: IDEs can auto-generate these methods correctly. Libraries like Apache Commons Lang (HashCodeBuilder/EqualsBuilder) or Lombok’s @EqualsAndHashCode reduce boilerplate and errors.
  • Test your implementation: Tools like EqualsVerifier can automatically check that your equals()/hashCode() obey the contract and handle edge cases (null, symmetry, etc.). Always include unit tests for complex equals()logic.
  • Domain-Driven Guidance: In Domain-Driven Design, value objects (objects defined by their data) should override equals()/hashCode(), whereas entities (objects with unique identity) may rely on their identity (ID) instead. This aligns with the idea that value objects are “equal by value” while entities are “equal by identity.”

Sources

Theory:

Examples:

Interview questions:

More deep dive?

--

--

Mayank Pant
Mayank Pant

Written by Mayank Pant

0 followers

My notes for future me