1. Introduction to the Set Interface

Set<E> is an interface in Java’s Collections Framework that represents a collection of distinct elements. Its key rule is that duplicate elements are not permitted. If a set already contains an element equal to the value being added, another copy is not inserted.

Set extends Collection<E>, so it inherits familiar operations such as add, remove, contains, size, and iteration. Its contract changes the meaning of adding an element: an attempted duplicate insertion leaves the set unchanged, and add returns false.

A Set is a natural choice when the program cares about membership or uniqueness rather than maintaining a sequence with positions. Examples include unique customer IDs, product tags, supported shipping countries, and distinct email addresses.

2. How Set Differs from List

Both List and Set are collection interfaces, but they make different promises.

FeatureList<E>Set<E>
Duplicate elementsAllowedNot allowed
Positional accessSupported through indexesNot supported
get(index)AvailableNot available
indexOf(element)AvailableNot available
Typical iteration orderDefined by the listDepends on implementation
Membership lookupUsually \(O(n)\) for ArrayListAverage \(O(1)\) for HashSet
Set algebraRequires additional logicSupported through collection methods

A shopping cart is often a List because the order of items may matter and duplicates may be valid. A set of customer IDs who placed at least one order today is naturally represented by a Set because each customer needs to appear only once.

Set does not define a numeric position for an element. You cannot use get(0) or insert an element at index 2. To access elements, use an iterator, enhanced for-each loop, or stream.

3. What Happens When a Duplicate Is Added?

Adding an element to a set has two possible outcomes:

  • If no equal element is present, the element is added and add returns true.
  • If an equal element is already present, the set remains unchanged and add returns false.

For example, if customer IDs 101, 204, 101, 307, and 204 are added, the set contains only 101, 204, and 307. Its size is 3, because size counts distinct elements rather than the number of insertion attempts.

Whiteboard
Loading diagram...


A Set does not normally throw an error just because a duplicate is offered. It simply declines to add a second equal element.

4. How Sets Decide Whether Elements Are Equal

The meaning of “duplicate” depends on equality. For ordinary Java objects, equals() defines logical equality. This is different from ==, which compares primitive values or checks whether two object references point to the same object.

For types such as String and Integer, Java already provides suitable equals() implementations. Two different String objects containing the same text are equal according to String.equals, so a set treats them as duplicates.

For a class written by the application, the default Object.equals() implementation generally treats two separate instances as unequal unless they are the same reference. If two Customer objects should count as the same customer based on an ID, the class must define that equality rule.

5. The equals() and hashCode() Contract

Hash-based sets such as HashSet use both hashCode() and equals() to manage membership efficiently.

  • equals() defines whether two objects represent the same logical value.
  • hashCode() supplies a hash value used to locate a likely storage bucket.

The essential contract is: if two objects are equal according to equals(), they must return the same hashCode() value. Unequal objects may share a hash code; that is permitted, though collisions can affect performance.

When creating a class intended for use in a hash-based set, override equals() and hashCode() consistently. Overriding equals() without a matching hashCode() can cause equal objects to behave as if they were distinct in a hash-based collection.

Another important practice is to avoid changing fields that participate in equality or hashing while an object is stored in a hash-based set. If its hash-relevant state changes, the set may no longer find it where it was originally stored.

Whiteboard
Loading diagram...


The exact mechanics differ between Set implementations. The hashCode() discussion is especially relevant to HashSet and LinkedHashSet; TreeSet generally relies on ordering and comparison instead.

6. Set Algebra: Union, Intersection, and Difference

Set operations are useful when comparing groups of distinct values. Java exposes three common operations through methods inherited from Collection.

Mathematical operationJava methodEffect on the receiver
Union, \(A \cup B\)addAll(B)Adds all elements from B
Intersection, \(A \cap B\)retainAll(B)Keeps only elements also found in B
Difference, \(A \setminus B\)removeAll(B)Removes elements that occur in B

These methods modify the set on which they are called. If the original set must remain available, make a copy first.

Consider two groups:

  • Bought product A: {101, 204, 307}
  • Bought product B: {204, 307, 410}

Their union is {101, 204, 307, 410}. Their intersection is {204, 307}. The difference A minus B is {101}.

7. Union with addAll()

Union contains every distinct element that appears in either set. The addAll method adds the other collection’s elements to the receiver. Since Set rejects duplicates, elements present in both sets appear only once in the result.

A copy is used so that the original boughtA set is not changed. After the operation, allBuyers represents the union.

8. Intersection with retainAll()

Intersection contains only elements that occur in both sets. retainAll removes from the receiver any element that does not appear in the supplied collection.

The result contains customers who bought both products. Because retainAll changes its receiver, copying boughtA first preserves the original data for other operations.

9. Difference with removeAll()

Difference A minus B contains elements present in A but absent from B.


This can answer a business question such as which customers bought product A but not product B. The receiver is changed, so a copy is useful when the original set is needed later.

9.1 Performance considerations for set operations

Methods such as retainAll and removeAll must examine elements and check whether they occur in the other collection. If the other collection is a List, each membership check may itself require a linear scan. For large data sets, using a Set as the other collection often makes membership tests much more efficient.

Actual performance depends on the implementation and the sizes of the collections, but choosing data structures with suitable membership complexity can prevent unnecessary repeated scans.

10. The Three Main Set Implementations

Set is an interface, so code creates an instance of a concrete implementation.

ImplementationIteration orderTypical add/contains costBest fit
HashSetNo guaranteed orderAverage \(O(1)\)General-purpose uniqueness
LinkedHashSetInsertion orderAverage \(O(1)\)Uniqueness while preserving insertion order
TreeSetSorted order\(O(\log n)\)Sorted traversal and range-based operations

10.1 HashSet

HashSet is a common default when the program needs distinct elements and does not require a particular iteration order. It uses hashing, providing average constant-time add and membership operations under typical conditions.

Do not rely on the order in which a HashSet prints its elements. That order is not guaranteed by the Set contract or by HashSet.

10.2 LinkedHashSet

LinkedHashSet maintains insertion order while still preventing duplicates. It is useful when output should follow the order in which distinct elements were first added.

It uses extra bookkeeping compared with a plain HashSet, but its average add and contains performance remains constant time under typical conditions.

10.3 TreeSet

TreeSet maintains elements in sorted order, either by natural ordering or by a supplied Comparator. Its main operations typically take \(O(\log n)\) time because it uses a tree-based structure.

Choose TreeSet when sorted iteration or range-oriented queries matter. Elements must be comparable under the selected ordering, and the ordering should be consistent with the application’s intended notion of uniqueness.

Whiteboard
Loading diagram...

11. Programming to the Set Interface

A common Java practice is to declare a variable using the interface and create the concrete implementation on the right-hand side.


This separates the program’s requirement—store unique customer IDs—from the chosen implementation. If the program later needs insertion order, it may be possible to replace HashSet with LinkedHashSet without changing code that relies only on the Set interface.

Implementation-specific behavior still matters. If code depends on sorted traversal, use a sorted-set abstraction or the appropriate concrete type rather than assuming every Set behaves the same way.

12. A Practical Customer-Analysis Example

Suppose an online store has a List of customer IDs who bought a notebook today. The List may contain duplicates because a customer can make multiple purchases. Converting it to a HashSet provides the number of distinct customers.

A second set can represent customers who used a discount. Copies of the first set can then be used to calculate intersection and difference:

  • The intersection identifies notebook buyers who also used a discount.
  • The difference identifies notebook buyers who did not use a discount.

This “deduplicate first, then compare sets” pattern is common for in-memory grouping and segmentation tasks. Copying before a mutating operation helps preserve the source data for additional calculations.

13. Common Mistakes

  1. Expecting a Set to preserve insertion order. Only implementations with an order guarantee, such as LinkedHashSet, should be relied on for that behavior.
  2. Trying to access a Set by index. Set has no get(index) or indexOf contract.
  3. Overriding equals() without hashCode(). Hash-based sets require a consistent contract.
  4. Using mutable equality fields as hash keys. Changing hash-relevant state while an object is stored can disrupt membership operations.
  5. Forgetting that set-algebra methods mutate the receiver. Copy the set when the original is needed later.
  6. Assuming all Set implementations have the same performance. HashSet, LinkedHashSet, and TreeSet have different ordering and complexity trade-offs.
  7. Passing a List into repeated membership checks for large set operations. A Set may be a better argument when fast membership testing matters.

14. Best Practices

  • Choose Set when uniqueness and membership matter more than index-based access.
  • Use HashSet as a general-purpose default when iteration order does not matter.
  • Use LinkedHashSet when insertion order must be preserved.
  • Use TreeSet when sorted traversal or range operations are required.
  • Implement equals() and hashCode() consistently for custom objects stored in hash-based sets.
  • Avoid mutating equality- or hash-relevant fields while an object is stored in a hash-based set.
  • Copy a set before addAll, retainAll, or removeAll if the original must be preserved.
  • Use the Set interface as the variable type when the code does not need implementation-specific features.

15. Quick Revision

  • Set<E> extends Collection<E> and prohibits duplicate elements.
  • add returns true for a new element and false when an equal element is already present.
  • Set does not provide positional methods such as get(index).
  • equals() defines logical equality; hash-based sets also rely on a consistent hashCode().
  • addAll performs union, retainAll performs intersection, and removeAll performs difference.
  • These set-algebra operations mutate the receiver.
  • HashSet has no guaranteed iteration order and average \(O(1)\) add/contains.
  • LinkedHashSet preserves insertion order.
  • TreeSet keeps elements sorted and typically performs core operations in \(O(\log n)\).
  • Choose the implementation according to uniqueness, ordering, and performance requirements.

16. Conclusion

The Set interface is the right abstraction when a program needs a collection of distinct elements rather than a positional sequence. Its duplicate rule makes membership and deduplication straightforward, while addAll, retainAll, and removeAll provide a practical foundation for union, intersection, and difference.

Correct equality behavior is essential, especially for custom classes stored in hash-based sets. Once that contract is understood, choosing between HashSet, LinkedHashSet, and TreeSet becomes a question of iteration order, sorted traversal, and expected operation costs.