Java HashMap Internals & equals() vs hashCode()
HashMap is one of the most widely used data structures in Java. It provides near constant-time performance (O(1)) for insert and lookup operations. However, many developers misunderstand how it works internally, especially when it comes to equals() and hashCode().
How HashMap Works Internally
Map<String, Integer> map = new HashMap<>();- First, hashCode() is called on the key to generate a hash value.
- The hash is converted into an index to find the bucket.
- The key-value pair is stored in that bucket.
- If multiple keys land in the same bucket, a collision occurs.
- Java uses LinkedList (before Java 8) or Tree structure (after Java 8) to handle collisions.
- equals() is used to compare keys inside the same bucket.
Why equals() and hashCode() Are Important
Java enforces a strict contract: if two objects are equal according to equals(), they must have the same hashCode(). If this rule is broken, HashMap and HashSet can behave incorrectly, leading to duplicate entries or failed lookups.
Real Example: Common Mistake (Employee Class)
Below is your example where equals() is overridden but hashCode() is missing. This leads to incorrect behavior in HashSet.
// Online Java Compiler
// Use this editor to write, compile and run your Java code online
import java.util.HashSet;
import java.util.Set;
class Employee{
private int id;
private String name;
Employee(int id,String name){
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Employee{id=" + id + ", name='" + name + "'}";
}
@Override
public boolean equals(Object obj) {
if(!(obj instanceof Employee)){
return false;
}
Employee temp = (Employee)obj;
return this.id == temp.id;
}
// @Override
// public int hashCode() {
// return java.util.Objects.hash(name);
// }
}
class Main {
public static void main(String[] args) {
Employee e1 = new Employee(451,"cool");
System.out.println(e1.toString());
Employee e2 = new Employee(451,"cool");
HashSet<Employee> setEmp = new HashSet<>();
setEmp.add(e1);
setEmp.add(e2);
System.out.println(setEmp.size());
System.out.println(e1.hashCode());
System.out.println(e2.hashCode());
System.out.println(e1.equals(e2));
}
}Correct Implementation
To fix this issue, we must override hashCode() properly using the same field used in equals().
@Override
public int hashCode() {
return java.util.Objects.hash(id);
}Best Practices
- Always override equals() and hashCode() together
- Use same fields in both methods
- Keep hashCode implementation simple and efficient
- Prefer Objects.hash() for readability
- Avoid using mutable fields in hash-based collections
Interview Tip
If you override equals() but not hashCode(), HashMap and HashSet will treat logically equal objects as different, leading to duplicates and incorrect lookups.
Final Summary
- hashCode() decides bucket location
- equals() checks object equality inside bucket
- Both must be consistent for correct HashMap behavior
- Wrong implementation leads to duplicates or missing data
Real-World Example
In real-world systems like HR management, banking, or e-commerce, incorrect implementation of equals() and hashCode() can lead to duplicate records. For example, two Employee objects with the same ID may be stored twice, causing issues in payroll processing or reporting.
This is why backend systems always enforce correct equality and hashing rules to ensure data consistency and system reliability.