JavaScriptSets

Set

A Set is a collection of unique values. Add the same value twice and it's stored once. Membership tests are fast — much faster than array.includes on a large array — and the iteration order is the order you added things. When you find yourself writing if (!arr.includes(x)) arr.push(x), you probably want a Set.

Creating a Set

JS
const empty = new Set();

const fromArray = new Set([1, 2, 3, 2, 1]);   // duplicates dropped
console.log(fromArray);                       // Set(3) { 1, 2, 3 }

const fromString = new Set("hello");
console.log(fromString);                      // Set(4) { 'h', 'e', 'l', 'o' }
Set(3) { 1, 2, 3 }
Set(4) { 'h', 'e', 'l', 'o' }

Any iterable works as the constructor argument: arrays, strings, other Sets, generators, iterators. Duplicates are filtered as values are added.

add, has, delete, size

The core methods are all O(1) (constant time) on average:

  • set.add(value) — adds, returns the set so you can chain.

  • set.has(value) — true/false membership check.

  • set.delete(value) — removes; returns true if it was there.

  • set.clear() — empties the set.

  • set.size — number of values (a property, not a method).

JS
const tags = new Set();
tags.add("javascript").add("web").add("javascript");

console.log(tags.size);          // 2
console.log(tags.has("web"));    // true
tags.delete("web");
console.log(tags.has("web"));    // false
2
true
false
Equality: SameValueZero

Sets compare values using the SameValueZero algorithm. In practice that means:

  • NaN equals itself (unlike ===) — so a Set holds at most one NaN.

  • +0 and -0 are treated as equal — one zero only.

  • Objects are compared by reference, never structurally. Two different object literals with the same shape are different values.

JS
const s = new Set();
s.add(NaN).add(NaN);             // only one NaN stored
console.log(s.size);             // 1

s.add({ id: 1 }).add({ id: 1 }); // two different objects
console.log(s.size);             // 3 — NaN + two object references
Iteration order

Sets iterate in insertion order — the order values were first added. This is guaranteed by the spec, which is why Set is also handy as a dedup-while-preserving-order primitive.

JS
const order = new Set();
order.add("c").add("a").add("b").add("a");

for (const v of order) console.log(v);
c
a
b

Re-adding an existing value doesn't reorder it. To "move" a value to the end, delete then add.

Iterating

A Set is iterable, so for...of and spread work directly. It also exposes values(), keys() (an alias of values()) and entries() (which yields [value, value] pairs, for API compatibility with Map).

JS
const nums = new Set([1, 2, 3]);

for (const n of nums) console.log(n);

nums.forEach(n => console.log(n));

const asArray = [...nums];
console.log(asArray);
1
2
3
1
2
3
[ 1, 2, 3 ]
Common idioms

A handful of one-liners come up often:

Dedup an array

JS
const unique = [...new Set([1, 2, 2, 3, 1, 4])];
console.log(unique);   // [ 1, 2, 3, 4 ]

Fast 'has any of these?' check

JS
const allowed = new Set(["admin", "editor", "viewer"]);

function canAccess(role) {
  return allowed.has(role);     // O(1)
}

Set algebra by hand

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

const union        = new Set([...a, ...b]);
const intersection = new Set([...a].filter(x => b.has(x)));
const difference   = new Set([...a].filter(x => !b.has(x)));

console.log([...union]);          // [ 1, 2, 3, 4 ]
console.log([...intersection]);   // [ 2, 3 ]
console.log([...difference]);     // [ 1 ]
Native Set operations
Recent runtimes (Node 22+, modern browsers) ship `a.union(b)`, `a.intersection(b)`, `a.difference(b)`, `a.symmetricDifference(b)`, `a.isSubsetOf(b)` and similar. If your target supports them, prefer them — they avoid the array conversions.
When to choose Set over array
  • Uniqueness matters — you want to store something at most once.

  • You need frequent has checks. On large collections, Set.has is dramatically faster than Array.includes.

  • Insertion order matters but indexes do not. Sets remember insertion order; they do not let you index by position.

Reach for an array when you need ordered indexing, sorting, or the rich map/filter/reduce API. You can always do [...set] to switch.

What Set is not
  • Not a way to dedup structurally: new Set([{a:1}, {a:1}]) has size 2 because each object is a different reference.

  • Not sorted: insertion order is preserved, not numeric or alphabetical order.

  • Not JSON-serialisable directly: JSON.stringify(new Set([1,2])) produces "{}". Convert with [...set] first.

One sentence to remember
A `Set` is a "list of unique values with fast membership tests" — perfect for dedup, allow-lists, and any time `includes` on an array is starting to feel slow.