HomepageExploring JavaScript (ES2025 Edition)
You can support this book: buy it or donate
(Ad, please don’t block.)

38 Sets (Set) ES6

The data structure Set manages a duplicate-free collection of values and provides fast membership checks and more.

38.1 Basic Set operations

38.1.1 Creating Sets

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');

38.1.2 Adding, removing, checking membership

.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);

38.1.3 Determining the size of a Set and clearing it

.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)

38.2 Combining Sets: union, intersection, difference, symmetric difference ES2025

There are four methods for combining two Sets.

38.2.1 Union: set.union(other) ES2025

The 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'])
);

38.2.2 Intersection: set.intersection(other) ES2025

The 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'])
);

38.2.3 Difference: set.difference(other) ES2025

The 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'])
);

Icon “exercise”Exercise: Implementing set.union(), set.intersection() and set.difference()

exercises/sets/set-union-intersection-difference_test.mjs

38.2.4 Symmetric difference: set.symmetricDifference(other) ES2025

The 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:

Icon “exercise”Exercise: Implementing set.symmetricDifference()

exercises/sets/set-symmetric-difference_test.mjs

38.3 Checking Set relationships: subset of, superset of, disjoint from ES2025

There are three methods for checking the relationships between two sets.

38.3.1 Subset: set.isSubsetOf(other) ES2025

set.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
);

38.3.2 Superset: set.isSupersetOf(other) ES2025

set.isSupersetOf(other) returns true if set contains all elements of other:

assert.deepEqual(
  new Set(['a', 'b', 'c']).isSupersetOf(new Set(['a', 'b'])),
  true
);

38.3.3 Disjoint: set.isDisjointFrom(other) ES2025

set.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
);

Icon “exercise”Exercises for Set relationship methods

38.4 Set-like objects (advanced)

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
}

38.4.1 Examples: finite Set-like objects

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,
);

38.4.2 Examples: infinite Set-like data

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:

38.4.3 FAQ: Set-like objects

Source: TC39 proposal

38.5 Iterating over Sets

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:

red
green
blue

As we can see, Sets preserve insertion order. That is, elements are always iterated over in the order in which they were added.

38.5.1 Converting a Set to an Array

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']
);

38.5.2 Mapping and filtering Sets via iterator methods

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:

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:

38.5.3 Example: combining Sets via iteration ES2025

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])
);

38.5.4 Grouping Set elements ES2024

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)

38.6 Examples of using Sets

38.6.1 Removing duplicates from an Array

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]
);

38.6.2 Creating a set of Unicode characters (code points)

Strings are iterable and can therefore be used as parameters for new Set():

assert.deepEqual(
  new Set('abc'),
  new Set(['a', 'b', 'c'])
);

38.7 Details of the Set API (advanced)

38.7.1 What Set elements are considered equal?

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

38.7.2 FAQ: Set API

38.8 Quick reference: Set

38.8.1 new Set()

38.8.2 Set.prototype.*: working with single Set elements

38.8.3 Set.prototype.*: working with all Set elements

38.8.4 Set.prototype.*: iterating and looping

38.8.4.1 Symmetry with Map

The following methods make the interface of Set symmetric with the interface of Map.

38.8.5 Set.prototype.*: combining two Sets ES2025

38.8.6 Set.prototype.*: checking Set relationships ES2025