Set

The Set interface — no duplicates, fast membership testing. HashSet, LinkedHashSet, TreeSet — when to use each.

Last reviewed: January 2025

·Java 8Java 11Java 17Java 21
✅
Quick Understanding: A Set is a collection that contains no duplicate elements. Use it when you need to ensure uniqueness or test membership quickly. HashSet is the default (O(1) add/contains/remove). LinkedHashSet preserves insertion order. TreeSet keeps elements sorted. The critical requirement: elements must have correct equals() and hashCode().

🔍 What Is It?

Set Interface

java.util.Set extends Collection but guarantees no duplicates:

  • →add(E e) — adds element; returns false if already present
  • →contains(Object o) — O(1) for HashSet, O(log n) for TreeSet
  • →remove(Object o) — removes element
  • →No index-based access (no get(int index))

Key Implementations:

  • →HashSet — backed by HashMap; O(1) average add/contains/remove; no ordering guarantee
  • →LinkedHashSet — HashSet + doubly-linked list; O(1) operations; preserves insertion order
  • →TreeSet — backed by TreeMap (Red-Black tree); O(log n) operations; always sorted; elements must be Comparable or provide Comparator
  • →EnumSet — specialized for enum types; extremely fast (bitset internally)
  • →Set.of() — immutable set (Java 9+)
  • →CopyOnWriteArraySet — thread-safe; backed by CopyOnWriteArrayList

🎯 Why Do We Need It?

Sets solve uniqueness problems:

  • →Deduplication — remove duplicates from a list
  • →Membership testing — is this user in the admin set? is this email already registered?
  • →Set operations — union, intersection, difference (for business logic)
  • →Tag systems — user has tags: Set<String> tags; fast contains() check

Critical: for HashSet to work correctly, elements must implement equals() and hashCode() consistently.

🏗️ What Happens in Real Projects?

Common enterprise patterns:

// Deduplication
List<String> withDups = Arrays.asList("a", "b", "a", "c", "b");
Set<String> unique = new LinkedHashSet<>(withDups);  // preserves order
List<String> deduped = new ArrayList<>(unique);

// Permission checking
Set<Permission> userPermissions = user.getPermissions();
if (userPermissions.contains(Permission.ADMIN)) { ... }

// Find common elements (intersection)
Set<String> teamA = Set.of("alice", "bob", "charlie");
Set<String> teamB = Set.of("bob", "charlie", "diana");
Set<String> both = new HashSet<>(teamA);
both.retainAll(teamB);  // {bob, charlie}

⚙️ How It Works

HashSet:

Internally a HashMap<E, PRESENT> — the element is the key. Uses hashCode() to find the bucket, then equals() to check for duplicates. Requires both to be consistent.

TreeSet:

Internally a TreeMap. Elements are sorted using natural ordering (Comparable) or a provided Comparator. Does NOT use equals()/hashCode() — uses compareTo() for equality. Two elements where compareTo() returns 0 are considered equal even if equals() returns false.

Critical: equals() and hashCode() contract:

If a.equals(b) is true, then a.hashCode() must equal b.hashCode(). If you override one, you must override both.

💻 Example

Set Operations in Enterprise Code — Practical Set usage for deduplication, membership, and set operations.

java
// HashSet — fastest, no order
Set<String> permissions = new HashSet<>();
permissions.add("READ");
permissions.add("WRITE");
permissions.add("READ");  // duplicate — silently ignored
System.out.println(permissions.size());  // 2

// LinkedHashSet — preserves insertion order
Set<String> orderedTags = new LinkedHashSet<>();
orderedTags.add("java");
orderedTags.add("spring");
orderedTags.add("microservices");
orderedTags.add("java");  // duplicate ignored, order preserved
System.out.println(orderedTags);  // [java, spring, microservices]

// Deduplication preserving order
public static <T> List<T> deduplicate(List<T> list) {
    return new ArrayList<>(new LinkedHashSet<>(list));
}

// Set operations
Set<String> admins = new HashSet<>(Set.of("alice", "bob", "charlie"));
Set<String> managers = new HashSet<>(Set.of("bob", "charlie", "diana"));

// Union (all unique elements from both)
Set<String> union = new HashSet<>(admins);
union.addAll(managers);  // {alice, bob, charlie, diana}

// Intersection (elements in both)
Set<String> intersection = new HashSet<>(admins);
intersection.retainAll(managers);  // {bob, charlie}

// Difference (in admins but not managers)
Set<String> difference = new HashSet<>(admins);
difference.removeAll(managers);  // {alice}

🤔 Common Confusions

❓ Why does my Set contain "duplicates" even though I added the same object?

You probably did not override equals() and hashCode() in your class. HashSet uses these to detect duplicates. Without overriding, two objects with the same data are considered different (default Object.equals() compares references). Always override both equals() and hashCode() for objects used in Sets or as Map keys.

❓ Why does TreeSet use compareTo() instead of equals() for duplicates?

TreeSet uses compareTo() (or Comparator.compare()) for all comparisons — including equality. If compareTo() returns 0, TreeSet considers them equal and rejects the second element. This can cause bugs if your compareTo() and equals() are inconsistent. The Comparable contract says: (a.compareTo(b) == 0) should be consistent with a.equals(b).

🚨 Common Production Issues

Mutable objects in HashSet — if you modify an object after adding it to a HashSet (changing the fields used in hashCode), the set cannot find the object anymore. Never mutate objects stored in a HashSet.

Missing hashCode override — if you override equals() but not hashCode(), two equal objects can end up in different buckets. Always override both together.

✅ Best Practices

Always override equals() and hashCode() together for objects used in Sets.

Use LinkedHashSet when you need deduplication with preserved insertion order.

Use TreeSet when you need a sorted set with navigation operations.

Use EnumSet for sets of enum values — it is the fastest possible implementation.

Use Set.of() for small immutable sets (constants, test data).

🏛️ Architect's Note

Senior Architect's Perspective

In domain modeling, Sets are natural for many-to-many relationships: User has Set<Role>, Order has Set<Tag>. In JPA, @ManyToMany relationships are typically modeled as Set (not List) to avoid duplicate join records. However, be careful with HashSet in JPA entities — Hibernate requires equals/hashCode based on a business key, not the generated database ID.

💬 Frequently Asked Questions

Is HashSet thread-safe?

No. HashSet is not synchronized. For concurrent access, use ConcurrentHashMap.newKeySet() (backed by ConcurrentHashMap), Collections.synchronizedSet(), or CopyOnWriteArraySet (for read-heavy workloads). In most cases, the best approach is to avoid sharing mutable sets between threads.

When should I use a Set vs a List with distinct()?

Use a Set when the data is inherently a set (no duplicates by definition, membership testing is important). Use stream().distinct() on a List when you have a list and need to deduplicate it temporarily. For large datasets, converting to a LinkedHashSet is more efficient than stream().distinct() since it avoids stream overhead.

🎯 Key Takeaways

1Set guarantees no duplicates — add() returns false if element already exists
2HashSet: O(1) operations, no order; LinkedHashSet: O(1) + insertion order; TreeSet: O(log n) + sorted
3Elements must have correct equals() and hashCode() for HashSet to work correctly
4TreeSet uses compareTo(), not equals() — keep them consistent
5EnumSet is the fastest Set for enum values
6Never mutate objects after adding them to a HashSet

🎯 Practice this topic in interviews

↗ Where this topic fits

Sources

Sources / References

Use these Core Java references to verify the concept in official docs and continue into the most relevant SplashRide pages.

Related SplashRide pages

SplashRide Core Java learning path

Browse the full Core Java topic collection and learning order on SplashRide.

Core Java Interview Prep

Practice Core Java interview questions and scenario-based answers.

Backend Engineer Career Path

A structured journey through Core Java, Spring Boot, production backend engineering, and interview preparation.

Full Stack Java Developer Career Path

A guided route through frontend foundations, Java backend engineering, delivery workflows, and interview readiness.

List

The List interface — ordered, indexed, allows duplicates. When to use ArrayList vs LinkedList, and the patterns senior developers rely on.

Java Map Interface Explained: HashMap, LinkedHashMap, and TreeMap

Learn the Java Map interface with HashMap, LinkedHashMap, TreeMap, ConcurrentHashMap, key-value lookup, ordering, performance, and real-world examples.