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).
🎯 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).
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); // 32Basic Operations
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
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
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:
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 b6Set vs Array — When to Use Which
| Need | Use |
|---|---|
Fast membership checks (.has()) on many elements | Set — O(1) average, vs Array.includes O(n) |
| Order matters and duplicates are meaningful | Array |
| Deduplication | Set |
Need indexed access (arr[2]) | Array |
// 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 call7Objects 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.
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 1To 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▾
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.
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
set.add("js"); // hash("js") -> bucket -> already points to an entry with value "js" -> no-opThis 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
Settriggers the same lazily-generated, GC-stable identity hash used byMapobject keys, so the object's membership survives the garbage collector relocating it in memory.
Why Set Beats Array for Membership Checks, Concretely
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 sizeThis 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
- Expecting Set to deep-compare objects — it only compares primitives by value; objects are always compared by reference.
- Using
set[0]for indexed access — Sets have no indices; convert to an array ([...set]) first if you need positional access. - Forgetting
Setis not JSON-serializable directly —JSON.stringify(new Set([1,2]))gives"{}"; convert with[...set]first. - 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
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
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.
- 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.
- 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)).
- 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.
- 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.
- 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.