The Set Interface in Java

The Set interface is part of the Java Collections Framework and represents a collection of unique elements. Unlike a List, a Set does not allow duplicate values.

The Set interface extends the Collection interface.

Key Characteristics

  • Stores only unique elements.
  • Does not allow duplicate values.
  • Part of the Java Collections Framework.
  • Useful when uniqueness is required.

Advertisement

Implementations of the Set Interface

Java provides several implementations of the Set interface.

HashSet

A HashSet stores elements using a hash table.

Features

  • Does not maintain insertion order.
  • Allows one null element.
  • Provides fast lookup, insertion, and deletion.
  • Average time complexity for add(), remove(), and contains() is O(1).

Best Use Cases

  • Removing duplicate values.
  • Fast searching.
  • When ordering is not important.

LinkedHashSet

LinkedHashSet extends HashSet by maintaining a doubly linked list of elements.

Features

  • Preserves insertion order.
  • Allows one null element.
  • Slightly slower than HashSet because of the linked list.

Best Use Cases

  • When uniqueness and insertion order are both required.

TreeSet

TreeSet implements the NavigableSet interface.

Features

  • Stores elements in sorted (ascending) order.
  • Does not allow null values.
  • Uses a Red-Black Tree internally.
  • Basic operations take O(log n) time.

Best Use Cases

  • Maintaining sorted data.
  • Range-based operations.
  • Automatically sorting elements.

HashSet vs LinkedHashSet vs TreeSet

Feature HashSet LinkedHashSet TreeSet
Duplicate elements ❌ Not allowed ❌ Not allowed ❌ Not allowed
Order maintained No Insertion order Sorted order
Internal structure Hash table Hash table + Linked List Red-Black Tree
Performance O(1) O(1) O(log n)
Allows null Yes (one) Yes (one) No

Real-Life Example

Think of a classroom attendance register.

Each student's name should appear only once.

A Set ensures that duplicate names cannot be added.


Real-Time Testing Example

In Selenium automation, a Set is commonly used to store:

  • Browser window handles
  • Unique URLs
  • Test IDs

Example:

Set<String> windows = driver.getWindowHandles();

Since every browser window has a unique handle, a Set is the ideal collection.


How HashMap Works Internally

HashMap is one of the most commonly used implementations of the Map interface.

It stores data as key-value pairs.


Internal Data Structure

Internally, a HashMap uses an array of buckets.

Each bucket stores one or more entries.

Each entry contains:

  • Key
  • Value
  • Hash value
  • Reference to the next node (if collision occurs)

Hashing

When a key-value pair is inserted:

  1. Java calls the key's hashCode() method.
  2. The hash code determines the bucket index.
  3. The entry is stored in that bucket.

Collision Handling

Sometimes two keys produce the same bucket index.

This is called a collision.

HashMap handles collisions using:

  • Linked List (before Java 8)
  • Balanced Tree (Red-Black Tree) (Java 8 and later, after a threshold)

This improves performance when many collisions occur.


Dynamic Resizing

As more elements are added:

  • HashMap automatically increases its capacity.
  • Existing entries are rehashed into the new bucket array.

This resizing is controlled by the load factor.


Performance

Operation Average Time Complexity
put() O(1)
get() O(1)
remove() O(1)
Worst case O(n)

NULL Values

A HashMap allows:

  • One null key.
  • Multiple null values.

Example

import java.util.HashMap;

public class Example {

    public static void main(String[] args) {

        HashMap<String, Integer> map = new HashMap<>();

        map.put("apple", 10);
        map.put("banana", 20);
        map.put("cherry", 30);

        System.out.println(map.get("apple"));
    }
}

Output

10

ArrayList vs LinkedList

Both classes implement the List interface, but they use different internal data structures.


ArrayList

ArrayList stores elements in a dynamic array.

Advantages

  • Fast random access.
  • Less memory usage.
  • Best for read-heavy applications.

LinkedList

LinkedList stores elements as doubly linked nodes.

Advantages

  • Fast insertion and deletion.
  • Efficient when elements are frequently added or removed.

ArrayList vs LinkedList Comparison

Feature ArrayList LinkedList
Internal structure Dynamic array Doubly linked list
Random access O(1) O(n)
Insertion/Deletion O(n) O(1) (at ends or with reference)
Memory usage Lower Higher
Best for Frequent reading Frequent insertion/deletion

Real-Life Example

ArrayList

Like books on a bookshelf.

You can directly pick any book by its position.

LinkedList

Like train coaches.

Adding or removing coaches is easy, but reaching a specific coach takes time.


Real-Time Testing Example

ArrayList

Used for storing:

  • Usernames
  • Passwords
  • Test data
  • Static configuration

LinkedList

Used for storing:

  • Execution logs
  • Retry steps
  • Undo history
  • Dynamic execution sequences

How to Make a Map Synchronized

By default, HashMap is not thread-safe.

You can make it synchronized using Collections.synchronizedMap().

Example

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class Example {

    public static void main(String[] args) {

        Map<String, Integer> map = new HashMap<>();

        Map<String, Integer> synchronizedMap =
                Collections.synchronizedMap(map);
    }
}

The returned map is thread-safe.


Important Note

When iterating over a synchronized map, you must explicitly synchronize on the map.

synchronized (synchronizedMap) {

    for (Map.Entry<String, Integer> entry :
            synchronizedMap.entrySet()) {

        System.out.println(entry);
    }
}

This prevents ConcurrentModificationException.


Real-Life Example

Imagine several people writing in a shared diary.

Only one person can write at a time to prevent overwriting each other's work.


Real-Time Testing Example

In Selenium automation frameworks, synchronized maps are often used to store:

  • Environment configuration
  • Parallel execution results
  • Shared test data

This ensures multiple threads can safely access shared data.


Collections.synchronizedMap() vs ConcurrentHashMap

Both provide thread safety but work differently.

Feature Collections.synchronizedMap() ConcurrentHashMap
Thread-safe Yes Yes
Locking Entire map Individual buckets/segments
Concurrency One thread at a time Multiple threads simultaneously
Performance Lower Higher

Real-Life Example

synchronizedMap

Only one person can use the shared diary at a time.

ConcurrentHashMap

Multiple people can write in different sections simultaneously.


Which One Should You Use?

  • Use Collections.synchronizedMap() when synchronization requirements are simple.
  • Use ConcurrentHashMap for high-performance multithreaded applications.

forEach vs Iterator

Both are used for traversing collections.


forEach

Introduced in Java 8.

Uses lambda expressions for concise iteration.

Example

list.forEach(item -> System.out.println(item));

Advantages

  • Simple.
  • Readable.
  • Less code.

Limitation

Cannot safely remove elements while iterating.


Iterator

Available since Java 1.2.

Uses:

  • hasNext()
  • next()

Example

Iterator<String> iterator = list.iterator();

while (iterator.hasNext()) {

    System.out.println(iterator.next());
}

Advantages

  • Can safely remove elements using iterator.remove().
  • More control over iteration.

forEach vs Iterator Comparison

Feature forEach Iterator
Introduced Java 8 Java 1.2
Syntax Lambda expression hasNext() and next()
Remove elements ❌ No ✅ Yes
Readability Cleaner More verbose

Real-Life Example

forEach

A teacher gives instructions to every student at once.

Iterator

A teacher checks each student individually and removes absent students from the attendance list.


Real-Time Testing Example

forEach

Useful for:

  • Printing test results.
  • Logging execution details.

Iterator

Useful for:

  • Removing failed test cases.
  • Filtering test data while iterating.

Try It: The Differences in One Program

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class CollectionsDemo {
    public static void main(String[] args) {
        List<String> tests = List.of("Login", "Search", "Checkout", "Login", "Cart");

        System.out.println("HashSet:       " + new HashSet<>(tests));        // no guaranteed order
        System.out.println("LinkedHashSet: " + new LinkedHashSet<>(tests));  // insertion order
        System.out.println("TreeSet:       " + new TreeSet<>(tests));        // sorted

        Map<String, String> config = new HashMap<>();
        config.put(null, "default");                  // one null key is allowed
        config.put("browser", null);                  // null values are allowed
        System.out.println("HashMap: " + config.get(null) + ", browser=" + config.get("browser"));
        try {
            new ConcurrentHashMap<String, String>().put("browser", null);
        } catch (NullPointerException e) {
            System.out.println("ConcurrentHashMap rejects null keys/values: NullPointerException");
        }

        List<String> results = new ArrayList<>(List.of("PASS", "FAIL", "PASS", "FAIL"));
        try {
            for (String r : results) if (r.equals("FAIL")) results.remove(r);   // modifying inside for-each
        } catch (ConcurrentModificationException e) {
            System.out.println("for-each + remove: ConcurrentModificationException");
        }

        results = new ArrayList<>(List.of("PASS", "FAIL", "PASS", "FAIL"));
        Iterator<String> it = results.iterator();
        while (it.hasNext()) if (it.next().equals("FAIL")) it.remove();          // safe
        System.out.println("Iterator.remove: " + results);

        results = new ArrayList<>(List.of("PASS", "FAIL", "PASS", "FAIL"));
        results.removeIf(r -> r.equals("FAIL"));                                   // simplest (Java 8+)
        System.out.println("removeIf:        " + results);
    }
}

Output (compiled and run with Java 21):

HashSet:       [Checkout, Search, Login, Cart]
LinkedHashSet: [Login, Search, Checkout, Cart]
TreeSet:       [Cart, Checkout, Login, Search]
HashMap: default, browser=null
ConcurrentHashMap rejects null keys/values: NullPointerException
for-each + remove: ConcurrentModificationException
Iterator.remove: [PASS, PASS]
removeIf:        [PASS, PASS]

What the run proves:

  • Order: all three sets dropped the duplicate "Login", but only LinkedHashSet kept insertion order and only TreeSet sorted. HashSet's order is an implementation detail — never assert on it in a test.
  • Nulls: HashMap accepts a null key and null values; ConcurrentHashMap throws NullPointerException — something to know before swapping one for the other in a parallel framework.
  • Removing while iterating: removing inside a for-each loop threw ConcurrentModificationException; Iterator.remove() and removeIf() did it safely.

Step through the loop and watch the list change in the Java Code Visualizer. How collections are used across a real framework: Collections in Your Automation Framework.

From Real Projects

Java has been the language of every automation framework I've worked with — Selenium, TestNG, POM classes and Apache POI for Excel data. Interviews for automation roles regularly include basic programs like this one, often followed by a question on OOP concepts. Practice writing it by hand and explaining how you'd test it. On Canolog, the many forms and screens across sales, inventory, finance and service are where keeping page details in POM classes and reusable steps in a business library paid off. Choose the collection by what you need: order, uniqueness or fast lookup by key.


📚 Official documentation: Oracle Java Tutorials: Collections

Thread-Safe Sets in Java (Concurrent Sets)

HashSet isn't thread-safe: two threads adding at once can lose entries or corrupt the set. That matters in test frameworks that run in parallel, for example when collecting failed test names or created test-data IDs from several threads. Java has no ConcurrentHashSet class, but there are four good options:

OptionOrderingBest for
ConcurrentHashMap.newKeySet()NoneThe default choice: fast, many threads reading and writing
Collections.synchronizedSet(new HashSet<>())NoneSimple cases with little contention; you must still lock manually while iterating
CopyOnWriteArraySetInsertion orderSmall sets that are read far more often than they change
ConcurrentSkipListSetSortedWhen you need sorted elements and thread safety
// Collect IDs of orders created by parallel tests, then clean them up once
private static final Set<String> createdOrderIds = ConcurrentHashMap.newKeySet();

@Test
public void placeOrder() {
    String id = api.createOrder(sampleOrder());
    createdOrderIds.add(id);          // safe from any thread
}

@AfterSuite
public void cleanUp() {
    createdOrderIds.forEach(api::deleteOrder);
}

FAQs

1. What Is the Set Interface Used For?

The Set interface stores unique elements and does not allow duplicates.

Common implementations include:

  • HashSet
  • LinkedHashSet
  • TreeSet

2. How Does a HashMap Work Internally?

A HashMap stores key-value pairs in an array of buckets.

  • The key's hashCode() determines the bucket.
  • Collisions are handled using a linked list or a balanced tree (Java 8+).
  • It automatically resizes based on the load factor.
  • It allows one null key and multiple null values.

3. What Is the Difference Between ArrayList and LinkedList?

  • ArrayList uses a dynamic array and provides fast random access.
  • LinkedList uses doubly linked nodes and provides faster insertion and deletion.

4. How Do You Make a Map Synchronized?

Wrap the map using Collections.synchronizedMap().

While iterating, synchronize on the map inside a synchronized block to avoid ConcurrentModificationException.


5. What Is the Difference Between Collections.synchronizedMap() and ConcurrentHashMap?

  • Collections.synchronizedMap() locks the entire map, allowing only one thread at a time.
  • ConcurrentHashMap locks only portions of the map, allowing multiple threads to work concurrently with better performance.

6. What Is the Difference Between forEach and Iterator?

  • forEach (Java 8) uses lambda expressions and is concise but cannot remove elements during iteration.
  • Iterator (Java 1.2) provides explicit control over iteration and supports safe element removal using iterator.remove().

7. What is a concurrent set in Java?

A Set that several threads can use at the same time without corrupting it. The usual choice is ConcurrentHashMap.newKeySet(); CopyOnWriteArraySet and ConcurrentSkipListSet cover read-heavy and sorted cases.


8. Is there a ConcurrentHashSet in Java?

No such class exists. Use ConcurrentHashMap.newKeySet(), which returns a thread-safe Set backed by a ConcurrentHashMap.