Set
Set is a built-in object that stores unique values of any type, whether primitive values or object references. It provides O(1) time complexity for adding, removing, and checking for the existence of elements, making it efficient for deduplication and membership testing.
Syntax
new Set()
new Set(iterable)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
iterable | Iterable | undefined | An iterable object whose elements will be added to the new Set |
Examples
The following examples cover the essential Set operations. You will see how to create a Set, populate it with values, test for membership, iterate over entries, and remove elements. Each example builds on the one before it, starting with basic add/has and finishing with clear.
Creating and populating a Set
const fruits = new Set();
fruits.add('apple');
fruits.add('banana');
fruits.add('orange');
// Adding a duplicate - ignored
fruits.add('apple');
console.log(fruits.size);
// 3
console.log(fruits.has('banana'));
// true
Adding elements one by one gives you full control over what enters the collection, but the real power of Set emerges when you construct it from an existing array. The constructor accepts any iterable and automatically discards duplicate values, so you get deduplication for free without writing a filter loop.
Creating a Set from an array
const nums = [1, 2, 3, 2, 1, 4, 5, 3];
const uniqueNums = new Set(nums);
console.log([...uniqueNums]);
// [1, 2, 3, 4, 5]
console.log(uniqueNums.size);
// 5
Construction from an array is the most common way to initialize a Set because it works with any data you already have in array form. Once you have a Set, you will want to walk through its elements. Unlike arrays, Sets maintain insertion order and support direct iteration with for...of.
Iterating over a Set
const colors = new Set(['red', 'green', 'blue']);
// Using for...of
for (const color of colors) {
console.log(color);
}
// red
// green
// blue
// Using forEach
colors.forEach(value => {
console.log(value);
});
Both for...of and forEach() iterate a Set’s values in insertion order. Note that forEach() passes the same value to both the first and second callback arguments — this is intentional, so the signature matches Map.forEach() where the second argument would be the key. After you have finished iterating, you can selectively remove items with delete() or empty the whole collection with clear().
Removing elements
const nums = new Set([1, 2, 3, 4, 5]);
nums.delete(3);
console.log(nums.has(3));
// false
nums.clear();
console.log(nums.size);
// 0
The basic operations — add, has, delete, clear — cover everyday use, but Set is most valuable when you apply it to collection-level problems. The patterns below show how to deduplicate arrays, compare collections, and combine sets with union, intersection, and difference.
Common Patterns
Deduplicating an array
const duplicateNames = ['Alice', 'Bob', 'Alice', 'Charlie', 'Bob'];
const uniqueNames = [...new Set(duplicateNames)];
// ['Alice', 'Bob', 'Charlie']
The spread operator into a new Set is the shortest way to strip duplicates from an array, but it only works with same-value equality. If you need case-insensitive deduplication — treating "Hello" and "hello" as the same entry — map the values to a canonical form first and then build the Set from the mapped list.
Case-insensitive string uniqueness
const strings = ['Hello', 'hello', 'HELLO', 'World'];
const unique = new Set(strings.map(s => s.toLowerCase()));
// Set { 'hello', 'world' }
Case-insensitive uniqueness is a variant of the deduplication pattern where you pre-process the data before feeding it to Set. The same technique applies whenever you want to deduplicate by a derived value — mapping to lowercase is just one example. A different need is finding which elements two collections share, which is the intersection pattern.
Finding intersection of two arrays
const a = [1, 2, 3, 4];
const b = [3, 4, 5, 6];
const setA = new Set(a);
const intersection = b.filter(x => setA.has(x));
// [3, 4]
Intersection uses the O(1) has() check to avoid the O(n²) cost of nested loops. Build a Set from the first array, then filter the second array by testing membership. The same idea works for removing duplicate characters from a string — cast the string to a Set, which iterates over each character, and join the result back together.
Removing duplicates from a string
const str = 'hello world';
const uniqueChars = [...new Set(str)].join('');
// 'helo wrd'
Removing duplicate characters from a string is a compact demonstration of Set’s iterable constructor — a string is iterable over its characters, so new Set(str) collects unique characters in one call. The intersection and deduplication patterns are special cases of the three fundamental set operations: union, intersection, and difference.
Set operations: union, intersection, difference
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);
// Union
const union = new Set([...setA, ...setB]);
// Set { 1, 2, 3, 4, 5, 6 }
// Intersection
const intersection = [...setA].filter(x => setB.has(x));
// [3, 4]
// Difference (A - B)
const difference = [...setA].filter(x => !setB.has(x));
// [1, 2]
Union creates a new collection containing every element from both inputs — new Set([...setA, ...setB]) is the standard one-liner. Intersection finds shared elements with filter and has(), and difference subtracts one set from another. All three patterns rest on Set’s O(1) membership tests, which is why they remain fast even with large data.
Performance Considerations
The Set object provides constant-time O(1) performance for its core operations: add(), delete(), has(), and size. This makes it significantly faster than using Array methods like includes() or indexOf() for large collections, where arrays would have O(n) linear search time.
When working with large datasets that require frequent membership checks or deduplication, prefer Set over Array. The trade-off is that Sets do not support random access by index—you must iterate through them to retrieve values.
Constructor details
The Set constructor accepts an optional iterable argument. If provided, all elements from the iterable will be added to the Set in order. This works with arrays, strings, other Sets, NodeLists, and any object implementing the iterable protocol.
// From a string - iterates over characters
const chars = new Set('hello');
// Set { 'h', 'e', 'l', 'o' }
// From a Map - iterates over [key, value] pairs
const map = new Map([['a', 1], ['b', 2]]);
const fromMap = new Set(map.keys());
// Set { 'a', 'b' }
The constructor’s flexibility means you can initialize a Set from almost any collection type without writing a loop. A less obvious detail is how Set decides whether a value already exists — it uses the SameValueZero algorithm, which has one important difference from ===.
Value equality uses SameValueZero
Set uses the SameValueZero algorithm to test whether two values are equal, which is almost identical to strict equality (===) with one difference: NaN is considered equal to itself.
const s = new Set();
s.add(NaN);
s.add(NaN); // duplicate — ignored
s.size; // 1
// Also: -0 and +0 are treated as equal
s.add(-0);
s.add(+0); // duplicate of -0
s.size; // 2 (NaN + one zero)
The NaN behavior is the notable departure from === — with strict equality, NaN === NaN is false, but Set treats two NaN values as duplicates. For -0 and +0, Set follows the === convention and considers them equal. Object references, on the other hand, are compared by identity, not by value. Two objects with identical properties count as distinct entries unless they are the same reference.
For objects, Set uses reference equality. Two objects with the same contents are not equal — they are different references:
const a = { x: 1 };
const b = { x: 1 };
const s = new Set([a, b]);
s.size; // 2 — different references, not duplicates
s.add(a); // a is already in the set — ignored
s.size; // still 2
WeakSet vs Set
WeakSet stores object references weakly, allowing them to be garbage-collected when no other references exist. It has no size property and cannot be iterated, so you trade enumeration capability for automatic memory cleanup. Use WeakSet when you need to tag objects without preventing their cleanup — for example, tracking which DOM nodes have already been processed so you can skip them on subsequent passes. The code below shows a typical guard pattern: check membership, and if the object is new, add it and proceed:
// WeakSet: membership test without preventing GC
const processed = new WeakSet();
function process(obj) {
if (processed.has(obj)) return;
processed.add(obj);
// ...
}
Set vs Array for membership tests
For frequent membership checks on large collections, Set is significantly faster than Array because has() runs in O(1) while Array.includes() is O(n):
const words = new Set(["apple", "banana", "cherry"]);
words.has("banana"); // O(1)
const arr = ["apple", "banana", "cherry"];
arr.includes("banana"); // O(n)
The crossover point depends on collection size and access frequency. For small arrays with infrequent lookups, the difference is negligible.