ECMAScript Set
Introduced in ECMAScript 2015 (ES6), the Set object provides a robust way to manage collections of unique values. Unlike a standard Array, which allows duplicate entries and requires $O(n)$ time complexity for membership checks, a Set inherently enforces uniqueness and offers an average time complexity of $O(1)$ for the has operation. This makes it an indispensable tool for developers dealing with deduplication, membership testing, and mathematical set operations.
A Set can be initialized as an empty collection or populated by passing an iterable object (such as an Array, String, or another Set) to its constructor. The constructor automatically iterates through the provided object and discards any duplicate values during initialization.
// Initializing an empty Set
const emptySet = new Set();
// Initializing with an Array (duplicates are automatically removed)
const numbers = new Set([1, 2, 2, 3, 4, 4]);
console.log(numbers); // Set(4) { 1, 2, 3, 4 }
// Initializing with a String
const characters = new Set('hello');
console.log(characters); // Set(4) { 'h', 'e', 'l', 'o' }
Core API and Operations
The Set instance provides a streamlined API for managing its contents. Below are the most frequently used methods and properties:
add(value): Adds a new element to the set. Notably, this method returns theSetobject itself, enabling method chaining.has(value): Returns a boolean indicating whether a specific value exists within the set.delete(value): Removes the specified element. It returnstrueif the element was successfully removed andfalseotherwise.clear(): Removes all elements from the set, resetting it to an empty state.size: A read-only property that returns the number of elements currently in the set. Note: Unlike arrays where you use.length,sizeis a property and should not be called as a function.
const mySet = new Set();
// Demonstrating method chaining
mySet.add(10).add(20).add(30);
console.log(mySet.has(20)); // true
console.log(mySet.size); // 3
mySet.delete(20);
console.log(mySet.has(20)); // false
mySet.clear();
console.log(mySet.size); // 0
The Logic of Uniqueness: SameValueZero
A critical aspect of Set is how it determines if two values are "the same." It employs the SameValueZero algorithm. This algorithm has specific behaviors that differ slightly from the strict equality operator (===):
NaNEquality: Unlike standard JavaScript equality whereNaN === NaNisfalse, theSettreatsNaNas equal toNaN. Therefore, aSetcan only contain oneNaNvalue.- Signed Zeros: Both
+0and-0are treated as the same value. - Object Reference: For non-primitive types (objects, arrays, functions), equality is determined by reference, not by content. Two different object literals with the same properties are considered distinct.
const uniquenessSet = new Set();
uniquenessSet.add(NaN);
uniquenessSet.add(NaN);
console.log(uniquenessSet.size); // 1
uniquenessSet.add(+0);
uniquenessSet.add(-0);
console.log(uniquenessSet.size); // 2 (Note: In some environments/specs, +0 and -0 are treated as identical; SameValueZero treats them as equal)
const objA = { id: 1 };
const objB = { id: 1 };
uniquenessSet.add(objA);
uniquenessSet.add(objB);
console.log(uniquenessSet.size); // 4 (objA and objB are different references)
Iterating Over a Set
Set objects maintain the insertion order of their elements. This means when you iterate over a set, the values will appear in the exact order they were added. You can traverse a Set using several methods:
for...ofloop: The most common and readable way to iterate.forEach(): Similar to theArray.prototype.forEach, though the callback arguments are slightly redundant becauseSetlacks keys.- Iterators:
keys(),values(), andentries().
const collection = new Set(['apple', 'banana', 'cherry']);
// Using for...of
for (const item of collection) {
console.log(item);
}
// Using forEach
collection.forEach((value) => {
console.log(value);
});
// Iterators
console.log([...collection.keys()]); // ['apple', 'banana', 'cherry']
console.log([...collection.values()]); // ['apple', 'banana', 'cherry']
console.log([...collection.entries()]); // [['apple', 'apple'], ['banana', 'banana'], ['cherry', 'cherry']]
Practical Use Case: Array Deduplication
One of the most common patterns in modern JavaScript is using a Set to remove duplicates from an array. By spreading a Set back into an array, you can achieve a clean, one-line deduplication.
const duplicateArray = [1, 2, 2, 3, 4, 4, 5];
// Method 1: Spread operator
const uniqueArray = [...new Set(duplicateArray)];
// Method 2: Array.from()
const uniqueArrayAlt = Array.from(new Set(duplicateArray));
console.log(uniqueArray); // [1, 2, 3, 4, 5]
Mathematical Set Operations
Historically, performing operations like unions or intersections required manual implementation using Array.filter and the spread operator. However, recent ECMAScript updates have introduced native methods to handle these mathematical operations directly.
The Manual Approach (Legacy/Compatibility)
const setA = new Set([1, 2, 3]);
const setB = new Set([3, 4, 5]);
const union = new Set([...setA, ...setB]);
const intersection = new Set([...setA].filter(x => setB.has(x)));
const difference = new Set([...setA].filter(x => !setB.has(x)));
The Modern Native Approach
Newer environments support built-in methods that are more expressive and efficient:
union(other): Returns a new set containing elements from both sets.intersection(other): Returns elements common to both sets.difference(other): Returns elements in the first set that are not in the second.symmetricDifference(other): Returns elements in either set, but not both.isSubsetOf(other),isSupersetOf(other), andisDisjointFrom(other): Boolean checks for set relationships.
const a = new Set([1, 2, 3]);
const b = new Set([3, 4, 5]);
console.log(a.union(b)); // Set(5) { 1, 2, 3, 4, 5 }
console.log(a.intersection(b)); // Set(1) { 3 }
console.log(a.symmetricDifference(b)); // Set(4) { 1, 2, 4, 5 }
Understanding WeakSet
WeakSet is a specialized version of Set designed for specific memory management scenarios. It differs from Set in several key ways:
- Object-Only: It can only store objects; primitive values (like strings or numbers) will throw a
TypeError. - Weak References: It holds "weak" references to its elements. This means if an object stored in a
WeakSethas no other references pointing to it, it can be reclaimed by the Garbage Collector (GC). This prevents memory leaks. - Non-Iterable: Because the GC can remove elements at any time,
WeakSetis not iterable. It does not have asizeproperty, aclear()method, or any way to loop through its contents.
WeakSet is ideal for "tagging" objects—for example, marking an object as "processed" or "authenticated" without preventing that object from being deleted from memory when it is no longer needed elsewhere.
const visitedObjects = new WeakSet();
let user = { name: "Alice" };
visitedObjects.add(user);
console.log(visitedObjects.has(user)); // true
user = null; // The object is now eligible for garbage collection
Summary and Best Practices
To use Set effectively in your development workflow, keep these guidelines in mind:
- Choose
Setfor Uniqueness: If your primary goal is to ensure no duplicate values exist or to perform high-speed membership checks,Setis superior toArray. - Choose
Arrayfor Order and Indexing: If you need to access elements by index (arr[0]), sort them, or perform frequent positional operations, stick withArray. - Deduplication Pattern: Use
[...new Set(array)]as your standard tool for cleaning up duplicate data. - Memory Safety: Use
WeakSetwhen you need to associate metadata with objects without interfering with their lifecycle. - Mind the Complexity: Remember that while
has()is $O(1)$, converting aSetto anArrayis an $O(n)$ operation. Avoid unnecessary conversions in performance-critical loops.