Engineering Notes
Home/Languages/JavaScript/Set
Languages · JavaScript

Set

Set is a collection of unique values — any attempt to add a value already present is silently ignored. Equality uses the same "SameValueZero" comparison as Map (like ===, except NaN equals itself).

💻 Languages● Intermediate⏱ 5 min read🛠️ 2 exercises🎤 5 interview Q&As

🎯 What you'll learn

  • What a Set Is
  • Basic Operations
  • The Fastest Way to Deduplicate an Array
  • Iterating a Set
  • Set Algebra (Union, Intersection, Difference)

1What a Set Is

Set is a collection of unique values — any attempt to add a value already present is silently ignored. Equality uses the same "SameValueZero" comparison as Map (like ===, except NaN equals itself).

js
const set = new Set([1, 2, 2, 3, 3, 3]);
console.log(set); // Set(3) { 1, 2, 3 } — duplicates automatically dropped
console.log(set.size); // 3

2Basic Operations

js
const set = new Set();
set.add("a").add("b").add("a"); // chainable, "a" added only once
set.has("a");    // true
set.delete("b");
set.size;         // 1
set.clear();

3The Fastest Way to Deduplicate an Array

js
const nums = [1, 2, 2, 3, 3, 3, 4];
const unique = [...new Set(nums)]; // [1, 2, 3, 4]

This is the idiomatic, single-line way to remove duplicates — far simpler than manually tracking seen values with filter + indexOf.

4Iterating a Set

js
const tags = new Set(["js", "web", "js"]);
for (const tag of tags) console.log(tag);   // "js", "web" — insertion order, guaranteed
tags.forEach(tag => console.log(tag));
[...tags];                                    // ["js", "web"]

5Set Algebra (Union, Intersection, Difference)

JavaScript doesn't provide these as built-in Set methods in most current runtimes (a newer proposal adds them natively in some engines), but they're trivial to implement with spread and filter:

js
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);

const union = new Set([...a, ...b]);                    // {1, 2, 3, 4}
const intersection = new Set([...a].filter(x => b.has(x))); // {2, 3}
const difference = new Set([...a].filter(x => !b.has(x)));  // {1} — in a but not b

6Set vs Array — When to Use Which

NeedUse
Fast membership checks (.has()) on many elementsSet — O(1) average, vs Array.includes O(n)
Order matters and duplicates are meaningfulArray
DeduplicationSet
Need indexed access (arr[2])Array
js
// Checking membership in a large collection repeatedly:
const validIds = new Set(largeIdArray); // build once
if (validIds.has(someId)) { /* O(1) */ } // vs largeIdArray.includes(someId), which is O(n) EVERY call

7Objects and Arrays in a Set

Set compares object/array values by reference, just like Map keys — two structurally identical objects are still treated as distinct members.

js
const set = new Set();
set.add({ id: 1 });
set.add({ id: 1 }); // a DIFFERENT object, even though it looks the same
console.log(set.size); // 2, not 1

To deduplicate objects by value (e.g. by an id field), dedupe manually with a Map keyed by that field instead.

Under the HoodHow Set Actually Stores Data▾
The index table hashes values to bucket indices; the entries table holds just values in insertion order
The index table hashes values to bucket indices; the entries table holds just values in insertion order

Set uses the exact same ordered-hash-table design as Map (see the Map topic's "Under the Hood" section for the full diagram and mechanics) — the only structural difference is that each entry stores just a key, with no associated value. In V8, Set is literally implemented as OrderedHashSet, a near-identical sibling class to OrderedHashMap, reusing the same index-table + entries-table pattern.

text
index table (hash -> entry index)         entries table (insertion order, values only)
┌───────────────────────────┐              ┌─────┬────────┐
│ bucket 0: -> entry 1        │              │ idx │ value   │
│ bucket 1: -> entry 0        │              ├─────┼────────┤
│ bucket 2: empty              │              │  0  │ "js"    │
└───────────────────────────┘              │  1  │ "web"   │
                                              └─────┴────────┘

.add() on a duplicate is a hash lookup, not a scan

js
set.add("js"); // hash("js") -> bucket -> already points to an entry with value "js" -> no-op

This is precisely why Set membership checks and duplicate rejection are O(1) average, not O(n) — .add() doesn't scan every existing member to check for a duplicate; it hashes the incoming value and checks whether that bucket already resolves to a matching entry, exactly like Map.prototype.has.

Resize, tombstones, and object identity hashing

All three mechanics from the Map topic apply identically here:

  • Resize/rehash — capacity roughly doubles and every live entry gets rehashed into a bigger index table once load factor crosses a threshold; amortized O(1) per .add().
  • Tombstones on delete — .delete() marks the entries-table slot as a hole rather than shifting everything after it; a resize later compacts these away.
  • Object identity hash — adding an object to a Set triggers the same lazily-generated, GC-stable identity hash used by Map object keys, so the object's membership survives the garbage collector relocating it in memory.

Why Set Beats Array for Membership Checks, Concretely

js
const bigArray = [...]; // 100,000 items
const bigSet = new Set(bigArray);

bigArray.includes(x); // O(n) — engine scans element by element until found or exhausted
bigSet.has(x);          // O(1) average — one hash computation + one bucket lookup, regardless of size

This difference is invisible for small collections and dominant for large ones — exactly the kind of Big-O gap the Time and Space Complexity topic describes.

⚠️ Common Pitfalls

  1. Expecting Set to deep-compare objects — it only compares primitives by value; objects are always compared by reference.
  2. Using set[0] for indexed access — Sets have no indices; convert to an array ([...set]) first if you need positional access.
  3. Forgetting Set is not JSON-serializable directly — JSON.stringify(new Set([1,2])) gives "{}"; convert with [...set] first.
  4. Re-adding an existing value expecting an error or a return signal — .add() on a duplicate is a silent no-op; check with .has() beforehand if you need to know whether it was new.

📋 Quick Reference

text
new Set(iterable)     [...new Set(arr)] → dedupe
set.add(v).has(v).delete(v).clear()
set.size
[...a].filter(x => b.has(x))  → intersection
[...a, ...b] as a Set          → union

🎤 Interview Q&A

Questions interviewers ask about this topic. Answer out loud first, then open a card to compare.

Q1What is a Set and when do you use it?core▾

A collection of unique values with O(1) average add, has and delete, preserving insertion order. Use for deduplication, membership checks, and tracking visited items.

Follow-ups interviewers ask
  • Dedupe an array? [...new Set(arr)].
Q2Why is Set.has faster than Array.includes?core▾

has is a hash lookup, O(1) average; includes scans the array, O(n). For repeated lookups, build a Set once.

Follow-ups interviewers ask
  • When does it matter? Lookups inside loops: O(n²) becomes O(n).
Q3How do you compute union, intersection and difference?core▾

Modern JS has a.union(b), a.intersection(b), a.difference(b), a.symmetricDifference(b), isSubsetOf. Older code: new Set([...a, ...b]), [...a].filter(x => b.has(x)).

Follow-ups interviewers ask
  • Availability? ES2025 Set methods in modern engines.
Q4How are object values compared in a Set?senior▾

By reference: two objects with identical content are both kept. For value-based uniqueness, use a Map keyed by an ID or serialized key.

Follow-ups interviewers ask
  • NaN in a Set? Only one NaN is kept.
Q5How do you iterate a Set?core▾

for…of, forEach, set.values(), spread into an array. Iteration follows insertion order.

Follow-ups interviewers ask
  • Why does Set have entries()? For API symmetry with Map; entries are [value, value].

🛠️ Practice exercises

Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.