skip to content

Set Uniqueness & CopyOnWriteArraySet

Uniqueness is entirely a function of the equals and hashCode contract, which is why a mutable element can corrupt a set. CopyOnWriteArraySet is the read-heavy concurrent variant built on copy-on-write.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

5

What is a Set in Java, and how does it decide that two elements are duplicates?

level: juniorimportance: must knowfreq 78%

answer

  1. No duplicates — mathematical set
  2. Duplicate decided by equals() (TreeSet: compareTo==0)
  3. add returns false if already present
  4. HashSet uses hashCode to find bucket
  5. HashSet/LinkedHashSet/TreeSet variants

basics

~10 s

A Set is a collection that holds no duplicate elements. When you add an element, the Set checks the ones already stored; if an equal one exists, the new element is not added.

solid answer

~40 s

A Set models the mathematical idea of a set: each distinct value is stored at most once, and duplicates are rejected. The Set does not invent its own notion of sameness; it delegates to the elements via equals() (and, for hash-based sets, hashCode() first). When you call add(x), the Set looks for an existing element e where x.equals(e); if found, add returns false and nothing changes, otherwise x is stored and add returns true. So a Set never holds two elements a and b with a.equals(b). Common implementations are HashSet (unordered, fast, uses hashCode/equals), LinkedHashSet (keeps insertion order), and TreeSet (sorted, uses compareTo/Comparator). Sets are ideal for membership tests and de-duplication.

code

java · 9 lines
java
Set<String> names = new HashSet<>();
System.out.println(names.add("Bob"));  // true  (added)
System.out.println(names.add("Bob"));  // false (duplicate, ignored)
System.out.println(names.size());       // 1
System.out.println(names.contains("Bob")); // true

// De-duplicate a list in one line:
List<Integer> nums = List.of(1, 2, 2, 3, 3, 3);
Set<Integer> unique = new HashSet<>(nums); // {1, 2, 3}

go deeper

for a junior

Knows a Set rejects duplicates and that equals() decides sameness; can name HashSet and use add/contains.

for a middle

Explains the role of hashCode in HashSet bucketing, distinguishes the three implementations and their ordering, and uses add's boolean return.

for a senior

Connects uniqueness to the equals/hashCode contract, contrasts equals-based vs comparator-based uniqueness (TreeSet), and warns about mutable elements breaking lookups.

for a principal

Reasons about choosing Set implementations for performance/memory trade-offs and API design (returning Set to express a uniqueness invariant), and the consequences of weak hash distribution at scale.

## What problem a Set solves Imagine a guest list where the same person must never appear twice. In Java the type that guarantees this is the **`Set`** interface (package `java.util`). A `Set` is a *collection* (an object holding many elements) with one defining rule: it contains **no duplicate elements**. ## What "duplicate" means A `Set` does not invent its own idea of sameness; it asks the **elements** through a method named **`equals`**. Every Java object inherits `equals` from the base class `Object`. By default `equals` does *reference equality*: two references are equal only if they point to the very same object in memory (`a == b`). Classes that represent a *value* (like `String`, `Integer`, or your own `Money`) usually **override** `equals` to compare *contents* instead. So whether two objects count as duplicates depends entirely on how their class defines `equals`. ## How adding works When you call `set.add(x)`: 1. The Set searches its existing elements for one, `e`, where `x.equals(e)` is `true`. 2. If such an `e` exists, `add` does **nothing** and returns `false` (the Set already has that value). 3. Otherwise `x` is stored and `add` returns `true`. This is why a `Set` never holds two elements `a` and `b` for which `a.equals(b)`. ## Hash-based sets need hashCode too The most common implementation, **`HashSet`**, would be slow if it compared `x` against *every* element. Instead it groups elements into "buckets" using each element's **`hashCode()`** (an `int` summary of the object). On `add`, `HashSet` computes `x.hashCode()`, jumps straight to the matching bucket, and only calls `equals` on the few elements there. This makes add/contains run in roughly **constant time** O(1) on average. The catch: `hashCode` and `equals` must agree (the *contract*) — see the dedicated question. ## The three everyday implementations - **`HashSet`** — no defined order, fastest, uses `hashCode`/`equals`. - **`LinkedHashSet`** — same speed class but remembers **insertion order** when you iterate. - **`TreeSet`** — keeps elements **sorted**; it decides duplicates by `compareTo`/`Comparator` returning 0, *not* by `equals`. ## Why you'd use a Set - **De-duplication**: `new HashSet<>(listWithDuplicates)` removes repeats. - **Fast membership tests**: `set.contains(x)` is O(1) for `HashSet`, far faster than scanning a `List`. - **Set algebra**: `addAll` (union), `retainAll` (intersection), `removeAll` (difference). ## A subtle gotcha If you put a **mutable** object into a `HashSet` and then change a field that affects its `hashCode`/`equals`, the Set may "lose" it — `contains` returns false even though it's still inside, because it now hashes to a different bucket. Prefer immutable elements.

  • What does Set.add return, and why is that useful?
    It returns true if the element was actually added (was not present) and false if an equal element already existed. You can use the boolean to detect duplicates without a separate contains() call.
  • If you need predictable iteration order, which Set do you pick?
    LinkedHashSet for insertion order, or TreeSet for sorted order. Plain HashSet gives no order guarantee and the order can even change across JVM runs or resizes.

A Set is a guest list: write each name once. The bouncer (equals) decides if 'Bob Smith' is already on the list before letting another in.

saying these in an interview costs you the question

  • Saying a Set keeps elements in the order you added them — only LinkedHashSet/TreeSet do; HashSet does not.
  • Thinking duplicates are detected by == reference equality rather than equals().
  • Claiming TreeSet uses equals() for uniqueness — it uses compareTo/Comparator returning 0.
  • Believing add throws or replaces on a duplicate — it silently returns false and leaves the Set unchanged.

context

open as a page

Explain the equals/hashCode contract and why violating it breaks a HashSet.

level: middleimportance: must knowfreq 85%

basics

~10 s

If two objects are equal by equals(), they must return the same hashCode(). If you break this, a HashSet can store duplicates or fail to find elements you put in.

open as a page

What goes wrong when you put mutable objects into a HashSet and then change them, and how do you avoid it?

level: middleimportance: should knowfreq 50%

basics

~20 s

If you change a field that affects an element's hashCode/equals after adding it, the HashSet may no longer find it — contains and remove can fail even though the object is still inside. Use immutable elements.

open as a page

What is CopyOnWriteArraySet and how does its copy-on-write mechanism work?

level: seniorimportance: should knowfreq 55%

basics

~10 s

CopyOnWriteArraySet is a thread-safe Set backed by a CopyOnWriteArrayList. Reads are lock-free, but every write copies the whole underlying array, so it suits small, read-heavy sets.

open as a page

How do you choose between CopyOnWriteArraySet, ConcurrentHashMap.newKeySet, and Collections.synchronizedSet for a thread-safe Set?

level: principalimportance: should knowfreq 48%

basics

~10 s

Pick by read/write ratio and size. CopyOnWriteArraySet for tiny read-mostly sets, ConcurrentHashMap.newKeySet for large or write-heavy concurrent sets, and synchronizedSet only as a simple fallback that locks every operation.

open as a page