DEV Community

Akshata
Akshata

Posted on

The HashSet Duplicate Illusion in Java 😏

I came across this interesting question during a technical interview.

public class Employee {
    public int id;
    public String name;
    public int salary;

    public Employee(String name, int id, int salary) {
        this.id = id;
        this.name = name;
        this.salary = salary;
    }

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

    @Override
    public String toString() {
        return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + "]";
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null || getClass() != obj.getClass())
            return false;
        Employee other = (Employee) obj;
        return id == other.id && Objects.equals(name, other.name) && salary == other.salary;
    }
}

Enter fullscreen mode Exit fullscreen mode
public class Main {
    public static void main(String[] args) {
        Employee e1 = new Employee("Sam", 1, 20000);
        Employee e2 = new Employee("Sam", 1, 20000);
        Employee e3 = new Employee("Sam", 2, 20000);

        HashSet<Employee> hs = new HashSet<>();
        hs.add(e1);
        hs.add(e2); // same as e1 β†’ won't be added
        hs.add(e3); // different id β†’ will be added

        System.out.println("Before update--->");
        for (Employee e : hs) {
            System.out.println(e);
        }

        // Change a field that is part of hashCode/equals
        e3.id = 1;

        System.out.println("After update--->");
        for (Employee e : hs) {
            System.out.println(e);
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Expected Output

Before update--->
Employee [id=2, name=Sam, salary=20000]
Employee [id=1, name=Sam, salary=20000]

After update--->
Employee [id=1, name=Sam, salary=20000]
Employee [id=1, name=Sam, salary=20000]

(Order may vary as HashSet does not guarantee iteration order)

❓ Why Does This Happen?
We know HashSet doesn’t allow duplicates, so why does the second print still show two identical employees?

When you add an element, HashSet:

  1. Calls hashCode() to find the correct bucket.
  2. If there’s already an element with the same hash, it calls equals() to check for equality.
  3. If they are equal, it does not add the element.

This check happens only at insertion.

When you change e3.id after it’s already in the set, it's hashCode() result changes.

However, the object remains in the bucket for its old hash value β€” HashSet does not automatically rehash or move elements when their data changes.

This causes the set to appear to contain duplicates and can also make contains() or remove() behave unexpectedly.

πŸ’― How to Prevent This

The safest way is to make objects immutable when they are used as keys in hash-based collections.

Example fix:

public class Employee {
    private final int id;
    private final String name;
    private final int salary;

    public Employee(String name, int id, int salary) {
        this.id = id;
        this.name = name;
        this.salary = salary;
    }

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

    @Override
    public String toString() {
        return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + "]";
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null || getClass() != obj.getClass())
            return false;
        Employee other = (Employee) obj;
        return id == other.id && Objects.equals(name, other.name) && salary == other.salary;
    }
}

Enter fullscreen mode Exit fullscreen mode

βœ… Changes:

  • Fields are private and final.
  • No setters provided β†’ prevents accidental modification.

πŸ”— Takeaway
When using HashSet or any HashMap keys:

  • Never mutate fields that participate in equals() and hashCode() after insertion.
  • Prefer immutable classes for keys.
  • If mutation is unavoidable:
    1. Remove the object from the set.
    2. Update it.
    3. Then re-add it.

Top comments (0)