Chapter 20 of 32

HashSet

Suppose you have a list of numbers:

10, 20, 30, 20, 40, 10

You want to store only the unique values.

The result should be:

10, 20, 30, 40

This is exactly the kind of problem a HashSet is designed to solve.

A HashSet is a collection that stores unique elements and uses hashing to provide fast operations such as insertion and searching.

What is a HashSet?

A HashSet stores individual values rather than key-value pairs.

For example:

10
20
30
40

Unlike a HashMap, there is no separate key and value.

The value itself is what the set stores.

The most important property of a HashSet is:

It does not allow duplicate elements.

For example:

import java.util.HashSet;

HashSet<Integer> numbers = new HashSet<>();

numbers.add(10);
numbers.add(20);
numbers.add(30);
numbers.add(20);

The value 20 was added twice, but the set contains it only once.

[10, 20, 30]

Creating a HashSet

We can create a HashSet like this:

HashSet<Integer> numbers = new HashSet<>();

Here:

Integer → Type of elements stored in the set

We can also create a set of strings:

HashSet<String> names = new HashSet<>();

Now it can store:

John
Jason
Alex
Michael

Adding Elements

We use the add() method:

numbers.add(10);
numbers.add(20);
numbers.add(30);

The set becomes:

10
20
30

If we add 20 again:

numbers.add(20);

nothing changes because 20 is already present.

Checking Whether an Element Exists

One of the most useful operations is:

contains()

For example:

if (numbers.contains(20)) {
    System.out.println("20 exists");
}

Output:

20 exists

HashSet is particularly useful when you need to quickly answer:

"Have I already seen this value?"

Removing an Element

We can remove an element using remove():

numbers.remove(20);

If the set was:

10
20
30

it becomes:

10
30

Finding the Size

We can use:

numbers.size()

For example:

System.out.println(numbers.size());

If the set contains:

10
20
30

the output is:

3

Checking Whether It Is Empty

Use:

numbers.isEmpty()

For example:

if (numbers.isEmpty()) {
    System.out.println("Set is empty");
}

Iterating Through a HashSet

We can use an enhanced for loop:

for (int number : numbers) {
    System.out.println(number);
}

For example, if the set contains:

10
20
30

the loop processes each unique element.

Remember that the iteration order of a normal HashSet is not guaranteed.

So you should not rely on elements appearing in insertion order.

HashSet Does Not Allow Duplicates

This is the most important property of a HashSet.

Consider:

HashSet<String> names = new HashSet<>();

names.add("John");
names.add("Jason");
names.add("John");
names.add("Alex");
names.add("Jason");

The final set contains only:

John
Jason
Alex

The duplicates are automatically ignored.

This makes HashSet extremely useful for removing duplicates.

Removing Duplicates from an Array

Suppose we have:

[10, 20, 10, 30, 20, 40]

We can use a HashSet:

import java.util.HashSet;

class Main {
    public static void main(String[] args) {

        int[] numbers = {
            10, 20, 10, 30, 20, 40
        };

        HashSet<Integer> unique = new HashSet<>();

        for (int number : numbers) {
            unique.add(number);
        }

        System.out.println(unique);
    }
}

The result contains only unique values:

[10, 20, 30, 40]

The order may differ because HashSet does not guarantee insertion order.

Finding Duplicate Elements

HashSet is also useful for detecting duplicates.

Suppose:

10, 20, 30, 20, 40

We can keep track of values we've already seen:

import java.util.HashSet;

class Main {
    public static void main(String[] args) {

        int[] numbers = {
            10, 20, 30, 20, 40
        };

        HashSet<Integer> seen = new HashSet<>();

        for (int number : numbers) {

            if (seen.contains(number)) {
                System.out.println(
                    "Duplicate: " + number
                );
            }

            seen.add(number);
        }
    }
}

Output:

Duplicate: 20

The logic is simple:

Have we seen this number?
        ↓
      Yes → Duplicate
        ↓
       No
        ↓
Add it to HashSet

This pattern appears frequently in DSA.

HashSet for Checking Unique Characters

Suppose we want to determine whether every character in a string is unique.

For example:

"abcd"

contains no duplicate characters.

We can use a HashSet:

import java.util.HashSet;

class Main {
    public static void main(String[] args) {

        String text = "abcd";

        HashSet<Character> seen = new HashSet<>();

        boolean unique = true;

        for (char ch : text.toCharArray()) {

            if (seen.contains(ch)) {
                unique = false;
                break;
            }

            seen.add(ch);
        }

        System.out.println(unique);
    }
}

Output:

true

If the string were:

"hello"

the second l would already exist in the set, so the result would be false.

HashSet for Array Intersection

HashSet can also help us find common elements between two arrays.

Suppose:

Array 1: [1, 2, 3, 4]
Array 2: [3, 4, 5, 6]

The common elements are:

3, 4

We can store the first array in a HashSet:

import java.util.HashSet;

class Main {
    public static void main(String[] args) {

        int[] first = {1, 2, 3, 4};
        int[] second = {3, 4, 5, 6};

        HashSet<Integer> set = new HashSet<>();

        for (int number : first) {
            set.add(number);
        }

        for (int number : second) {

            if (set.contains(number)) {
                System.out.println(number);
            }
        }
    }
}

Output:

3
4

The HashSet lets us check whether each element from the second array exists in the first array efficiently.

HashSet and Hashing

HashSet uses hashing internally.

The basic idea is:

Element
   ↓
hashCode()
   ↓
Hash calculation
   ↓
Bucket

When we call:

set.add(20);

Java uses the object's hash information to determine where it should be stored.

Later, when we call:

set.contains(20);

the hash information helps Java quickly locate the appropriate area.

Hash Collisions

Different objects can sometimes produce the same hash location.

For example:

Value A → Bucket 3
Value B → Bucket 3

This is called a hash collision.

A HashSet handles collisions internally.

You don't normally need to manually manage them when using Java's HashSet, but understanding collisions is important for understanding how hash-based data structures work.

Time Complexity

For a well-distributed HashSet, common operations are generally O(1) on average.

Operation

Average

Worst Case

add()

O(1)

O(n)*

remove()

O(1)

O(n)*

contains()

O(1)

O(n)*

size()

O(1)

O(1)

*The exact worst-case behavior depends on the implementation and collision structure.

For DSA, the important point is:

add()      → O(1) average
contains() → O(1) average
remove()   → O(1) average

HashSet vs ArrayList

Both can store multiple elements, but they are designed for different purposes.

Feature

ArrayList

HashSet

Allows duplicates

Yes

No

Maintains insertion order

Yes

No guarantee

Index-based access

Yes

No

Fast lookup by value

O(n)

O(1) average

Allows null

Yes

Yes, one null

For example, if you need:

[10, 20, 30, 20]

and want to preserve the duplicate 20, use an ArrayList.

If you want:

10, 20, 30

with unique values and fast membership checks, a HashSet is often a better choice.

HashSet vs HashMap

The difference is very important.

A HashSet stores:

Value

For example:

10
20
30

A HashMap stores:

Key → Value

For example:

101 → John
102 → Jason
103 → Alex

So:

HashSet → Unique values
HashMap → Key-value pairs

A Real-Life Example

Imagine a guest list for a party.

You receive names from different sources:

John
Jason
John
Alex
Jason
Michael

You don't want to invite the same person twice.

You can use a HashSet:

John
Jason
Alex
Michael

Every name is stored only once.

This is exactly the kind of problem where a HashSet is useful.

A Complete Example

Let's create a simple program that finds duplicate names:

import java.util.HashSet;

class Main {
    public static void main(String[] args) {

        String[] names = {
            "John",
            "Jason",
            "Alex",
            "John",
            "Michael",
            "Jason"
        };

        HashSet<String> seen = new HashSet<>();

        for (String name : names) {

            if (seen.contains(name)) {

                System.out.println(
                    "Duplicate: " + name
                );

            } else {

                seen.add(name);
            }
        }
    }
}

Output:

Duplicate: John
Duplicate: Jason

The HashSet remembers every name we've already encountered.

When we see a name again, contains() tells us that it already exists.

When Should You Use HashSet?

HashSet is particularly useful when you need to:

  • Store only unique elements

  • Remove duplicates

  • Quickly check whether an element exists

  • Detect duplicate values

  • Track elements you've already seen

  • Find common elements between collections

  • Solve membership-based DSA problems

A useful question to ask yourself during a DSA problem is:

"Do I just need to know whether I've already seen this value?"

If the answer is yes, a HashSet may be exactly what you need.

The Main Idea

A HashSet is a collection that stores unique elements and uses hashing to provide fast average-time operations.

The most important methods are:

add()       → Add an element
contains()  → Check whether an element exists
remove()    → Remove an element
size()      → Get number of elements
isEmpty()   → Check whether the set is empty

The most common DSA pattern looks like:

See an element
      ↓
Already in HashSet?
   ↙          ↘
 Yes          No
  ↓            ↓
Duplicate    Add it

HashSet is ideal when you need to store unique values and quickly check whether a value has already been seen.

Once you understand add(), contains(), and remove(), you'll be able to use HashSet for a huge number of DSA problems involving duplicates, uniqueness, and fast membership checks.