Set) ES6
The data structure Set manages a duplicate-free collection of values and provides fast membership checks and more.
There are three common ways of creating Sets.
First, we can use the constructor without any parameters to create an empty Set:
const emptySet = new Set();
assert.equal(emptySet.size, 0);
Second, we can pass an iterable (e.g., an Array) to the constructor. The iterated values become elements of the new Set:
const set = new Set(['red', 'green', 'blue']);
Third, the .add() method adds elements to a Set and is chainable:
const set = new Set()
.add('red')
.add('green')
.add('blue');
.add() adds an element to a Set.
const set = new Set();
set.add('red');
.has() checks if an element is a member of a Set.
assert.equal(set.has('red'), true);
.delete() removes an element from a Set.
assert.equal(set.delete('red'), true); // there was a deletion
assert.equal(set.has('red'), false);
.size contains the number of elements in a Set.
const set = new Set()
.add('foo')
.add('bar');
assert.equal(set.size, 2)
.clear() removes all elements of a Set.
set.clear();
assert.equal(set.size, 0)
There are four methods for combining two Sets.
set.union(other) ES2025The result of set.union(other) is a Set that has the values of both set and other:
assert.deepEqual(
new Set(['a', 'b']).union(new Set(['b', 'c'])),
new Set(['a', 'b', 'c'])
);
set.intersection(other) ES2025The result of set.intersection(other) is a Set that has the values that set and other have in common:
assert.deepEqual(
new Set(['a', 'b']).intersection(new Set(['b', 'c'])),
new Set(['b'])
);
set.difference(other) ES2025The result of set.difference(other) is a Set that has the values that are only in set:
assert.deepEqual(
new Set(['a', 'b']).difference(new Set(['b', 'c'])),
new Set(['a'])
);
Exercise: Implementing
set.union(), set.intersection() and set.difference()
exercises/sets/set-union-intersection-difference_test.mjs
set.symmetricDifference(other) ES2025The result of set.symmetricDifference(other) is a Set that has the values that are only in set or only in other:
assert.deepEqual(
new Set(['a', 'b']).symmetricDifference(new Set(['b', 'c'])),
new Set(['a', 'c'])
);
assert.deepEqual(
new Set(['a', 'b']).symmetricDifference(new Set(['c', 'd'])),
new Set(['a', 'b', 'c', 'd'])
);
What does symmetric difference mean? These are equivalent definitions of the symmetric difference:
set − other) ∪ (other − set)
set or only in other. The formula makes it clear why the symmetric difference is both symmetric and a difference.
set ∪ other) − (set ∩ other)
set and other – except for the elements that are in both Sets.
set xor other
set is inverted. Everything that is outside set is not changed.
Exercise: Implementing
set.symmetricDifference()
exercises/sets/set-symmetric-difference_test.mjs
There are three methods for checking the relationships between two sets.
set.isSubsetOf(other) ES2025set.isSubsetOf(other) returns true if all elements of set are in other:
assert.deepEqual(
new Set(['a', 'b']).isSubsetOf(new Set(['a', 'b', 'c'])),
true
);
set.isSupersetOf(other) ES2025set.isSupersetOf(other) returns true if set contains all elements of other:
assert.deepEqual(
new Set(['a', 'b', 'c']).isSupersetOf(new Set(['a', 'b'])),
true
);
set.isDisjointFrom(other) ES2025set.isDisjointFrom(other) returns true if set and other have no elements in common:
assert.deepEqual(
new Set(['a', 'b', 'c']).isDisjointFrom(new Set(['x'])),
true
);
Exercises for Set relationship methods
exercises/sets/set-is-equal-to_test.mjs
set.isSubsetOf(): exercises/sets/is-subset-of_test.mjs
set.isDisjointFrom(): exercises/sets/is-disjoint-from_test.mjs
A Set method whose parameter is another Set other does not require other to be an actual Set; it only has to be Set-like and have the following methods:
interface SetLike<T> {
/** Can be `Infinity` (see next section). */
size: number;
has(key: T): boolean;
/** Returns an iterator for the elements in `this`. */
keys(): Iterator<T>; // only method `.next()` is required
}
Let’s implement a simple Set-like object and use it with methods whose parameters are “other Sets”:
const setLike = {
size: 1,
has(x) { return x === 'b' },
* keys() { yield 'b' },
};
assert.deepEqual(
new Set(['a', 'b', 'c']).difference(setLike),
new Set(['a', 'c']),
);
assert.deepEqual(
new Set(['a', 'b', 'c']).difference(setLike),
new Set(['a', 'c']),
);
assert.equal(
new Set(['a', 'b', 'c']).isSupersetOf(setLike),
true,
);
assert.equal(
new Set(['b']).isSubsetOf(setLike),
true,
);
Maps are also Set-like:
const setLike = new Map([['b', true]]);
assert.deepEqual(
new Set(['a', 'b', 'c']).difference(setLike),
new Set(['a', 'c']),
);
assert.equal(
new Set(['a', 'b', 'c']).isSupersetOf(setLike),
true,
);
The .size of other can be Infinity. That means we can work with infinite Sets:
const evenNumbers = {
has(elem) {
return (elem % 2) === 0;
},
size: Infinity,
keys() {
throw new TypeError();
}
};
assert.deepEqual(
new Set([0, 1, 2, 3]).difference(evenNumbers),
new Set([1, 3])
);
assert.deepEqual(
new Set([0, 1, 2, 3]).intersection(evenNumbers),
new Set([0, 2])
);
This works because these methods only invoke other.keys() if other.size is smaller than this.size.
Only two methods don’t support other being an infinite Set:
union
symmetricDifference
Why use the interface SetLike for other?
other can be a data structure that is not a Set. It was chosen as a compromise between accepting only Sets and all iterable objects.
Why does JavaScript always enforce the full interface SetLike for other and throws an exception if a property is missing or has the wrong dynamic type?
Why was method .keys() chosen for iterating over elements?
Map. Two other Set methods would have been nicer but can’t be used with Maps:
.[Symbol.iterator]() returns key-value pairs.
.values() is incompatible with the Map method .has() (which accepts keys, not values).
Source: TC39 proposal
Sets are iterable and the for-of loop works as we’d expect:
const set = new Set(['red', 'green', 'blue']);
for (const x of set) {
console.log(x);
}
Output:
redgreenblue
As we can see, Sets preserve insertion order. That is, elements are always iterated over in the order in which they were added.
Sets are iterable, which is why we can use Array.from() to convert a Set to an Array:
const set = new Set(['red', 'green', 'blue']);
assert.deepEqual(
Array.from(set),
['red', 'green', 'blue']
);
We can also perform the conversion via an iterator:
assert.deepEqual(
set.values().toArray(),
['red', 'green', 'blue']
);
set.values() returns in iterator for the values of set.
.toArray() creates an Array with the iterated values.
Sets don’t have a method .map(). But we can borrow the one that iterators have:
const set = new Set([1, 2, 3]);
const mappedSet = new Set( // (A)
set.values().map(x => x * 2) // (B)
);
assert.deepEqual(
mappedSet,
new Set([2, 4, 6])
);
The previous code shows a common pattern for using iterator methods with Sets:
set.values() returns an iterator for set (line B).
.map() is an iterator method that returns an iterator (line B).
Set which accepts any iterable as a parameter and uses it to fill the new Set with values.
Filtering Sets works the same way:
const set = new Set([1, 2, 3, 4, 5]);
const filteredSet = new Set(
set.values().filter(x => (x % 2) === 0)
);
assert.deepEqual(
filteredSet,
new Set([2, 4])
);
What if we can’t use iterator methods? Then we can switch to Array methods:
Array.from(set)
set.values().
Instead of the Set methods, we can also use iteration to combine Sets:
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
// Union
assert.deepEqual(
new Set([...a, ...b]),
new Set([1, 2, 3, 4])
);
// Intersection
assert.deepEqual(
new Set(a.values().filter(x => b.has(x))),
new Set([2, 3])
);
// Difference
assert.deepEqual(
new Set(a.values().filter(x => !b.has(x))),
new Set([1])
);
Grouping via Object.groupBy() and Map.groupBy() works for any iterable object and therefore for Sets:
assert.deepEqual(
Object.groupBy(
new Set([0, -5, 3, -4, 8, 9]),
x => Math.sign(x)
),
{
'0': [0],
'-1': [-5,-4],
'1': [3,8,9],
__proto__: null,
}
);
More information: “Grouping iterables ES2024” (§32.8)
Converting an Array to a Set and back, removes duplicates from the Array:
const arr = [1, 2, 1, 2, 3, 3, 3];
const noDuplicates = Array.from(new Set(arr));
assert.deepEqual(
noDuplicates, [1, 2, 3]
);
Strings are iterable and can therefore be used as parameters for new Set():
assert.deepEqual(
new Set('abc'),
new Set(['a', 'b', 'c'])
);
As with Map keys, Set elements are compared similarly to ===, with the exception of NaN being equal to itself.
> const set = new Set([NaN, NaN, NaN]);
> set.size
1
> set.has(NaN)
true
As with ===, two different objects are never considered equal (and there is no way to change that at the moment):
> const set = new Set();
> set.add({});
> set.size
1
> set.add({});
> set.size
2
Why do Sets have a .size, while Arrays have a .length?
.size, while Arrays have a .length?” (§36.7.4).
Why are some method names verbs and others nouns? This is a rough general rule:
this – e.g.: set.add() and set.clear()
set.values() and set.union()
Setnew Set()new Set(iterable) ES6
values, then an empty Set is created.
const set = new Set(['red', 'green', 'blue']);
Set.prototype.*: working with single Set elementsSet.prototype.add(value) ES6
value to this Set.
this, which means that it can be chained.
const set = new Set(['red']);
set.add('green').add('blue');
assert.deepEqual(
Array.from(set), ['red', 'green', 'blue']
);
Set.prototype.delete(value) ES6
value from this Set.
true if something was deleted and false, otherwise.
const set = new Set(['red', 'green', 'blue']);
assert.equal(set.delete('red'), true); // there was a deletion
assert.deepEqual(
Array.from(set), ['green', 'blue']
);
Set.prototype.has(value) ES6
Returns true if value is in this Set and false otherwise.
const set = new Set(['red', 'green']);
assert.equal(set.has('red'), true);
assert.equal(set.has('blue'), false);
Set.prototype.*: working with all Set elementsget Set.prototype.size ES6
Returns how many elements there are in this Set.
const set = new Set(['red', 'green', 'blue']);
assert.equal(set.size, 3);
Set.prototype.clear() ES6
Removes all elements from this Set.
const set = new Set(['red', 'green', 'blue']);
assert.equal(set.size, 3);
set.clear();
assert.equal(set.size, 0);
Set.prototype.*: iterating and loopingSet.prototype.values() ES6
Returns an iterable over all elements of this Set.
const set = new Set(['red', 'green']);
for (const x of set.values()) {
console.log(x);
}
Output:
redgreen
Set.prototype[Symbol.iterator]() ES6
Default way of iterating over Sets. Same as .values().
const set = new Set(['red', 'green']);
for (const x of set) {
console.log(x);
}
Output:
redgreen
Set.prototype.forEach(callback, thisArg?) ES6
forEach(
callback: (value: T, key: T, theSet: Set<T>) => void,
thisArg?: any
): void
Feeds each element of this Set to callback(). value and key both contain the current element. This redundancy was introduced so that this callback has the same type signature as the callback of Map.prototype.forEach().
We can specify the this of callback via thisArg. If we omit it, this is undefined.
const set = new Set(['red', 'green']);
set.forEach(x => console.log(x));
Output:
redgreen
MapThe following methods make the interface of Set symmetric with the interface of Map.
Set.prototype.entries(): Iterable<[T,T]> ES6
Mainly exists so that Sets and Maps have similar interfaces: Each Set element is viewed as a key-value entry whose key and value are both that element:
> new Set(['a', 'b', 'c']).entries().toArray()
[ [ 'a', 'a' ], [ 'b', 'b' ], [ 'c', 'c' ] ]
.entries() enables us to convert a Set to a Map:
const set = new Set(['a', 'b', 'c']);
const map = new Map(set.entries());
assert.deepEqual(
Array.from(map.entries()),
[['a','a'], ['b','b'], ['c','c']]
);
Set.prototype.keys(): Iterable<T> ES6
Mainly exists so that Sets and Maps have similar interfaces: Each Set element is viewed as a key-value entry whose key and value are both that element. Therefore the result of .keys() is the same as the result of .values():
> new Set(['a', 'b', 'c']).keys().toArray()
[ 'a', 'b', 'c' ]
Set.prototype.*: combining two Sets ES2025Set.prototype.union(other) ES2025
Set<T>.prototype.union(other: SetLike<T>): Set<T>
This method returns a Set that is the union of this and other. It contains a value if it is in this or other.
assert.deepEqual(
new Set(['a', 'b']).union(new Set(['b', 'c'])),
new Set(['a', 'b', 'c'])
);
other doesn’t have to be a Set, it only has to be Set-like and have the property .size and the methods .has(key) and .keys(). Sets and Maps both fulfill those requirements.
Set.prototype.intersection(other) ES2025
Set<T>.prototype.intersection(other: SetLike<T>): Set<T>
This method returns a Set that is the intersection of this and other. It contains a value if it is in this or other.
assert.deepEqual(
new Set(['a', 'b']).intersection(new Set(['b', 'c'])),
new Set(['b'])
);
other doesn’t have to be a Set, it only has to be Set-like and have the property .size and the methods .has(key) and .keys(). Sets and Maps both fulfill those requirements.
Set.prototype.difference(other) ES2025
Set<T>.prototype.difference(other: SetLike<T>): Set<T>
This method returns a Set that is the difference between this and other. It contains a value if it is in this but not in other.
assert.deepEqual(
new Set(['a', 'b']).difference(new Set(['b', 'c'])),
new Set(['a'])
);
other doesn’t have to be a Set, it only has to be Set-like and have the property .size and the methods .has(key) and .keys(). Sets and Maps both fulfill those requirements.
Set.prototype.symmetricDifference(other) ES2025
Set<T>.prototype.symmetricDifference(other: SetLike<T>): Set<T>
This method returns a Set that is the symmetric difference between this and other. It contains a value if it is only in this or only in other.
assert.deepEqual(
new Set(['a', 'b']).symmetricDifference(new Set(['b', 'c'])),
new Set(['a', 'c'])
);
other doesn’t have to be a Set, it only has to be Set-like and have the property .size and the methods .has(key) and .keys(). Sets and Maps both fulfill those requirements.
For more information on this method, see its section in this chapter.
Set.prototype.*: checking Set relationships ES2025Set.prototype.isSubsetOf(other) ES2025
Set<T>.prototype.isSubsetOf(other: SetLike<T>): boolean
Returns true if all elements of this are in other:
assert.deepEqual(
new Set(['a', 'b']).isSubsetOf(new Set(['a', 'b', 'c'])),
true
);
Set.prototype.isSupersetOf(other) ES2025
Set<T>.prototype.isSupersetOf(other: SetLike<T>): boolean
Returns true if this contains all elements of other:
assert.deepEqual(
new Set(['a', 'b', 'c']).isSupersetOf(new Set(['a', 'b'])),
true
);
Set.prototype.isDisjointFrom(other) ES2025
Set<T>.prototype.isDisjointFrom(other: SetLike<T>): boolean
Returns true if this and other have no elements in common:
assert.deepEqual(
new Set(['a', 'b', 'c']).isDisjointFrom(new Set(['x'])),
true
);