Blog post

Union, intersection, difference, and more are coming to JavaScript Sets

Blog Author Phil Nash

Phil Nash

Developer Advocate JS/TS

7 min read

  • JavaScript
A stylistic Venn diagram

The JavaScript Set was introduced to the language in the ES2015 spec, but it has always seemed incomplete. That's about to change.


Sets are collections of values where each value may only appear once. In the ES2015 version of the Set, the available functionality revolved around creating, adding to, removing from, and checking the membership of a Set. If you wanted to operate on or compare more than one set, you had to write your own functions. Thankfully, TC39—the committee established to work on the ECMAScript spec—and the browsers have been working on this. We are now seeing functions like union, intersection and difference in JavaScript implementations.


Before we look at the new functionality, let's recap what JavaScript Sets can do now and then we'll jump into the new Set functions and the JavaScript engines that support them below.

What do ES2015 JavaScript Sets do?

Let's take a look at what the ES2015 version of the JavaScript Set can do. It's easiest to do so with some examples.


You can construct a Set without any arguments, which gives you an empty Set. Or, you can provide an iterable, like an array, to initialise the Set.


const languages = new Set(["JavaScript", "TypeScript", "HTML", "JavaScript"]);

Sets can only contain unique values, so the Set above has three members. You can check this with the size property.

languages.size;

// => 3

You can add more elements to the Set with the add function. Adding an element that is already in the Set doesn't do anything.

languages.add("JavaScript");

languages.add("CSS");

languages.size;

// => 4

You can remove elements from the Set with delete.

languages.delete("TypeScript");

languages.size;

// => 3

You can check if an element is a member of the Set with the has function. One of the benefits of a Set is that this check can be done in constant time (O(1)), whereas the time to check if an element is in an Array varies by the length of the Array (O(n)). Using Sets for tasks like this is a clean way to write intentional efficient code.

languages.has("JavaScript");

// => true

languages.has("TypeScript");

// => false

You can loop through a Set's elements using forEach or a for...of loop. Elements are sorted in the order they were added to the Set.

languages.forEach(element => console.log(element));

// "JavaScript"

// "HTML"

// "CSS"

You can also get an iterator from the Set using the keys and values functions (which are actually equivalent) as well as the entries function.


Finally, you can empty a Set with the clear function.

languages.clear();

languages.size;

// => 0

That's a good reminder of what you can do with the ES2015 spec version of a Set

  • Sets provide methods to deal with a collection of unique values
  • It is efficient to add elements to a Set and to test for their presence in the Set
  • Converting an Array or other iterable to a Set is an easy way to filter out duplicates


This implementation misses out on operations between Sets, though. You might want to create a Set that contains all the items from two other Sets (a union of two Sets), find out what two Sets have in common (intersection), or find out what isn't present in one Set that is in another (difference). Until recently, you would have had to provide your own functions.

What are the new Set functions?

The Set methods proposal adds the following methods to Set instances: union, intersection, difference, symmetricDifference, isSubsetOf, isSupersetOf, and isDisjointFrom.


Some of these methods are akin to some SQL joins, which we will use to illustrate the results alongside the code. Let's see some examples of what each function does.


You can try out any of the code examples below in Chrome 122+ or Safari 17+.

Set.prototype.union(other)

A union of sets is a set that contains all the elements present in either set.

const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);

const backEndLanguages = new Set(["Python", "Java", "JavaScript"]);

const allLanguages = frontEndLanguages.union(backEndLanguages);

// => Set {"JavaScript", "HTML", "CSS", "Python", "Java"}

In this example, all the languages from the first two sets are in the third set. As with other methods that add elements to the Set, duplicates are removed.


This is the equivalent of a SQL FULL OUTER JOIN between two tables.

A venn diagram of a union, showing that everything in both sets is selected.

Set.prototype.intersection(other)

An intersection is a set that contains all the elements that are present within both sets. 

const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);

const backEndLanguages = new Set(["Python", "Java", "JavaScript"]);

const frontAndBackEnd = frontEndLanguages.intersection(backEndLanguages);

// => Set {"JavaScript"} 

"JavaScript" is the only element present in both the sets here.


An intersection is like an INNER JOIN.


A Venn diagram showing an intersection of two sets. Only the items in both sets are selected.

Set.prototype.difference(other)

The difference between the set you are working with and another set is all the elements present in the first set and not present in the second set.

const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);

const backEndLanguages = new Set(["Python", "Java", "JavaScript"]);

const onlyFrontEnd = frontEndLanguages.difference(backEndLanguages);

// => Set {"HTML", "CSS"} 

const onlyBackEnd = backEndLanguages.difference(frontEndLanguages);

// => Set {"Python", "Java"}

In finding the difference between sets, it matters which set you call the function on and which is the argument. In the example above, removing the back-end languages from the front-end languages results in "JavaScript" being removed and returning "HTML" and "CSS" in the resultant set. Whereas removing the front-end languages from the back-end languages still results in "JavaScript" being removed, and returns "Python" and "Java".


A difference is like performing a LEFT JOIN.

A Venn diagram showing the difference between two sets, only items in the first set are selected.

Set.prototype.symmetricDifference(other)

The symmetric difference between two sets is a set that contains all the elements that are in one of the two sets, but not both.

const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);

const backEndLanguages = new Set(["Python", "Java", "JavaScript"]);

const onlyFrontEnd = frontEndLanguages.symmetricDifference(backEndLanguages);

// => Set {"HTML", "CSS", "Python", "Java"} 

const onlyBackEnd = backEndLanguages.symmetricDifference(frontEndLanguages);

// => Set {"Python", "Java", "HTML", "CSS"}

In this case, the elements in the resultant sets are the same, but note that the order is different. Set order is determined by the order the elements are added to the set and the set on which the function is performed will have its elements added first.


A symmetric difference is like a FULL OUTER JOIN excluding any elements that are in both tables.

A Venn diagram of the symmetric difference, showing that everything is selected apart from the items in both sets.

Set.prototype.isSubsetOf(other)

A set is a subset of another set if all the elements in the first set appear in the second set.

const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);

const declarativeLanguages = new Set(["HTML", "CSS"]);

declarativeLanguages.isSubsetOf(frontEndLanguages);

// => true

frontEndLanguages.isSubsetOf(declarativeLanguages);

// => false

A set is also a subset of itself.

frontEndLanguages.isSubsetOf(frontEndLanguages);

// => true

Set.prototype.isSupersetOf(other)

A set is a superset of another set if all the elements in the second set appear in the first set. It is the opposite relationship of being a subset.

const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);

const declarativeLanguages = new Set(["HTML", "CSS"]);

declarativeLanguages.isSupersetOf(frontEndLanguages);

// => false

frontEndLanguages.isSupersetOf(declarativeLanguages);

// => true

A set is also a superset of itself.

frontEndLanguages.isSupersetOf(frontEndLanguages);

// => true

Set.prototype.isDisjointFrom(other)

Finally, a set is disjoint from another set if they have no elements in common.

const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);

const interpretedLanguages = new Set(["JavaScript", "Ruby", "Python"]);

const compiledLanguages = new Set(["Java", "C++", "TypeScript"]);

interpretedLanguages.isDisjointFrom(compiledLanguages);

// => true

frontEndLanguages.isDisjointFrom(interpretedLanguages);

// => false

The interpreted languages and compiled languages in these sets do not overlap, so the sets are disjoint. The front-end languages and the interpreted languages do overlap with the element "JavaScript", so they are not disjoint.

Support

As of writing this, the proposal stands at stage 3 in TC39's process and Safari 17 (released in September 2023) and Chrome 122 (February 2024) have shipped implementations of these methods. Edge follows Chrome closely and Firefox Nightly has support behind a flag, I would expect both of these browsers to ship support soon too.


Bun also uses Safari's JavaScriptCore engine and thus already supports these new functions. Support in Chrome means that it has been added to the V8 JavaScript engine and will be adopted by Node.js soon.


Hopefully, this means the proposal will graduate to stage 4 of the process, perhaps even in time to join the ES2024 spec before it is finalised.

Polyfills

While you need older JavaScript engine support, there are polyfills you can use to upgrade to spec-compliant implementations of these functions. They are available in core-js or as individual packages per function in the es-shims project (for example, the set.prototype.union package can be used for union functionality).


If you've written your own implementation of any of these functions, I would recommend first upgrading to the polyfills, before phasing them out as the support becomes more widespread.

Sets no longer feel incomplete

The JavaScript Set has long been incomplete but these 7 new functions round out the implementation nicely. Building functionality like this into the language means we have to rely less on dependencies or our own implementations and can focus on the problems we are trying to solve.


This is just one of a full pipeline of stage 3 proposals before TC39 right now. Check out the list to see what else might be coming to JavaScript next. I've got my eye on Temporal and Decorators, both of which could change the way we write major parts of our JavaScript.


Get new blogs delivered directly to your inbox!

Stay up-to-date with the latest Sonar content. Subscribe now to receive the latest blog articles. 

By submitting this form, you agree to the storing and processing of your personal data as described in the Privacy Policy and Cookie Policy. You can withdraw your consent by unsubscribing at any time.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

SonarLint

Clean Code from the start in your IDE

Up your coding game and discover issues early. SonarLint takes linting to another level empowering you to find & fix issues in real time.

Install SonarLint
SonarQube

Clean Code for teams and enterprises

Empower development teams with a self-hosted code quality and security solution that deeply integrates into your enterprise environment; enabling you to deploy clean code consistently and reliably.

SonarCloud

Clean Code in your cloud workflow

Enable your team to deliver clean code consistently and efficiently with a code review tool that easily integrates into the cloud DevOps platforms and extend your CI/CD workflow.