JavaScript Set Methods: union, intersection, difference & Co.
AI generated
JS
() =>
JavaScript · ES2025 · Data structures · Set
JavaScript Set Methods
union(), intersection(), difference() natively in the browser

For years, set operations in JavaScript had to be laboriously assembled with filter() and has(). The new JavaScript Set Methods solve this problem with a clean, readable API, directly on the Set object, without external libraries and without workarounds.

14 min read union · intersection · difference · symmetricDifference · isSubsetOf Chrome 122+ · Firefox 127+ · Safari 17+

1. Why JavaScript went without Set Methods for so long

The Set object has been available in JavaScript since ES2015 and elegantly solves the problem of duplicate values. What was missing, though, were the classic set operations: union, intersection, difference and symmetric difference. In other languages such as Python, Ruby or Java, these operations belong to the standard repertoire of every collection class. In JavaScript, developers had to write their own implementations for years, functional but often error-prone and hard to read.

The TC39 proposal process for JavaScript Set Methods started in 2018 and went through all four stages to finalization. The delay was mainly due to API design questions: should the methods be mutating or non-mutating? Should they only accept other Set instances or arbitrary iterables? The final decision was to design the Set Methods as non-mutating, each method returns a new set instead of changing the original. This follows the immutability principle of modern JavaScript APIs. And they accept arbitrary set-like objects, not only exact Set instances.

2. union(): the union of two sets

The union() method returns a new set containing all elements from both sets, without duplicates, since sets by definition contain no duplicate elements. The resulting set contains exactly the values that occur in set A or in set B. The method is symmetric: a.union(b) and b.union(a) produce sets with identical content, even though the internal order may differ.

In practice, union() shows up anywhere two independent sources need to be merged without creating duplicates. A typical scenario: a user has permissions from their role and from explicitly assigned rights. The Set of effective permissions is the union of both sets. Without Set Methods, this would be a spread operator into a new set: new Set([...rolePermissions, ...explicitPermissions]). With union(), the intent is clearer and the result is the same.


// Set Methods, union, intersection, difference examples
const frontend = new Set(['React', 'Vue', 'Angular', 'Svelte']);
const backend = new Set(['Node.js', 'Python', 'Go', 'React']); // React appears in both

// union(): all technologies from both sets, no duplicates
const allTech = frontend.union(backend);
// Set { 'React', 'Vue', 'Angular', 'Svelte', 'Node.js', 'Python', 'Go' }

// intersection(): only technologies present in both sets
const fullstack = frontend.intersection(backend);
// Set { 'React' }

// difference(): technologies in frontend but NOT in backend
const frontendOnly = frontend.difference(backend);
// Set { 'Vue', 'Angular', 'Svelte' }

// symmetricDifference(): technologies in exactly one of the two sets
const unique = frontend.symmetricDifference(backend);
// Set { 'Vue', 'Angular', 'Svelte', 'Node.js', 'Python', 'Go' }

// Chain multiple Set Methods
const result = frontend
  .union(new Set(['TypeScript', 'Rust']))
  .difference(new Set(['Angular']));
// Set { 'React', 'Vue', 'Svelte', 'TypeScript', 'Rust' }

3. intersection(): computing the intersection

The intersection() method returns a new set that contains only the elements present in both sets. Mathematically, this is the intersection A ∩ B. It is the most useful of the new Set Methods for filtering tasks: given a set of IDs from a database and a set of allowed IDs, which IDs is the user allowed to see? The answer is the intersection, computed with a single intersection() call.

The internal algorithm of the Set Methods implementation is clever: if one of the sets is significantly smaller, the algorithm iterates over the smaller set and checks for each element whether it occurs in the larger set. That is more efficient than fully traversing both sets. Anyone intersecting two sets of very different sizes benefits from this optimization automatically. In practice, this makes a measurable difference on large data sets compared to the manual array workaround.

4. difference(): the difference set and what it means

The difference() method returns a new set containing the elements from the first set that are not present in the second set. Mathematically, this is A \ B or A - B. The method is not commutative: a.difference(b) and b.difference(a) generally return different results. That makes intuitive sense: "elements in A that are not in B" is a different question from "elements in B that are not in A".

A concrete use case: an e-commerce system has a set of all product IDs in the catalog and a set of the IDs already in the cart. The difference gives the products that are not yet in the cart, useful for recommendation algorithms or for displaying items not yet purchased. With the new JavaScript Set Methods, this calculation is a single readable line. Earlier implementations used filter(id => !cart.has(id)) on arrays, which required a conversion between set and array.

5. symmetricDifference(): elements in exactly one set

The symmetricDifference() method returns a set of all elements that occur in exactly one of the two sets, in other words, the elements that are not in the intersection. Mathematically, this is A △ B = (A ∪ B) \ (A ∩ B). This Set Method is the mathematically most elegant of the group and is used in scenarios where you want to find out what changed between two states.

The typical use case for symmetricDifference() is diff algorithms for sets: given the set of active feature flags before a deployment session and after, which flags were enabled or disabled? The symmetric difference delivers exactly this set of changed flags, without distinguishing whether they were added or removed. If you need that information, you combine it with difference() in both directions: added flags are after.difference(before), removed flags are before.difference(after).


// Practical permission system using Set Methods
const rolePermissions = new Set(['read', 'write', 'comment']);
const adminPermissions = new Set(['read', 'write', 'delete', 'manage-users', 'export']);
const explicitGrants = new Set(['export', 'audit-log']);
const explicitRevokes = new Set(['comment']);

// Effective permissions: (role union explicit grants) minus revokes
const effectivePermissions = rolePermissions
  .union(explicitGrants)
  .difference(explicitRevokes);
// Set { 'read', 'write', 'export', 'audit-log' }

// What permissions does this user lack compared to admin?
const missingPermissions = adminPermissions.difference(effectivePermissions);
// Set { 'delete', 'manage-users' }

// Are all effective permissions a subset of admin?
const isSafeSubset = effectivePermissions.isSubsetOf(adminPermissions);
// false, 'audit-log' is not in adminPermissions

// Feature flag diff, what changed between deployments?
const flagsBefore = new Set(['dark-mode', 'new-checkout', 'beta-search']);
const flagsAfter = new Set(['dark-mode', 'new-checkout', 'stable-search', 'ai-recommend']);

const added = flagsAfter.difference(flagsBefore);    // Set { 'stable-search', 'ai-recommend' }
const removed = flagsBefore.difference(flagsAfter);  // Set { 'beta-search' }
const changed = flagsBefore.symmetricDifference(flagsAfter); // Set { 'beta-search', 'stable-search', 'ai-recommend' }

6. isSubsetOf(), isSupersetOf(), isDisjointFrom()

Alongside the methods that return new sets, the JavaScript Set Methods offer three boolean check methods. isSubsetOf(other) checks whether all elements of the caller set are also present in the other set, mathematically A ⊆ B. isSupersetOf(other) is the inverse and checks whether the caller set contains all elements of the other. isDisjointFrom(other) checks whether the two sets share no common elements, in other words whether their intersection is empty.

These check methods are especially useful for validations and guards. Example: an upload handler only accepts certain MIME types. Instead of checking each received type individually, you can check whether the set of received MIME types is a subset of the allowed types. receivedTypes.isSubsetOf(allowedTypes) returns true if all received types are allowed. That is semantically more precise and more readable than an every() check over an array. Like all other Set Methods, all three boolean Set Methods also accept arbitrary set-like objects.

7. Practical examples: filters, permissions, diff algorithms

The JavaScript Set Methods shine especially in three domains: permission systems, filter logic and change processing. In a role-based access system, you compute a user's effective rights from the union of their role permissions minus the explicit revokes. Then you check with requiredPermissions.isSubsetOf(effectivePermissions) whether the user is allowed to perform an operation. This approach is more declarative and precise than a cascade of includes() checks.

For filter applications in UI components, sets are ideal when tags, categories or attributes are used as filter criteria. An article with the tags {'React', 'TypeScript', 'Performance'} and an active filter {'React', 'TypeScript'}: the article appears when activeFilter.isSubsetOf(articleTags) is true, meaning all filter conditions must be met. For OR logic (at least one filter matches), you check whether the intersection is non-empty: activeFilter.intersection(articleTags).size > 0. These expressions are immediately readable for developers because the Set Methods directly mirror the mathematical logic.

8. Performance: Set Methods vs. array workarounds

The performance advantage of the JavaScript Set Methods comes from two sources. First, set lookups have O(1) complexity compared to O(n) for array includes. An array workaround like a.filter(x => b.includes(x)) has O(n²) complexity, because the entire length of B is searched for each element of A. The equivalent Set Method call a.intersection(b) has O(min(|A|, |B|)) complexity. For sets with thousands of elements, this difference is substantial.

The second source of performance comes from avoiding array conversions. Many workarounds follow the pattern: set to array (via spread) to filter/map to a new set. Every conversion costs time and memory. The Set Methods operate directly on set internals without intermediate conversions. For small sets, the difference is negligible. For sets with thousands to millions of elements, typical in data processing pipelines, caches or ID management, the asymptotically better complexity is a real advantage that translates into milliseconds.


// Performance comparison, Set Methods vs. Array workarounds
// Both produce identical results, but Set Methods are O(min(n,m)) vs O(n*m)

const setA = new Set(Array.from({ length: 10000 }, (_, i) => i));
const setB = new Set(Array.from({ length: 8000 }, (_, i) => i * 1.2 | 0));

// Old workaround: O(n * m), array includes is O(m) for each element
const arrA = [...setA];
const arrB = [...setB];
const intersectionOld = new Set(arrA.filter((x) => arrB.includes(x)));

// New Set Method: O(min(n, m)), native hash lookup
const intersectionNew = setA.intersection(setB);

// Both results are equivalent, but the Set Method is dramatically faster at scale

// Chaining Set Methods, readable multi-step pipeline
const allowedCountries = new Set(['DE', 'AT', 'CH', 'LU', 'LI']);
const activeCountries = new Set(['DE', 'AT', 'US', 'GB', 'FR']);
const blockedCountries = new Set(['FR', 'GB']);

const targetCountries = allowedCountries
  .intersection(activeCountries)     // DE, AT
  .union(new Set(['CH']))            // DE, AT, CH
  .difference(blockedCountries);     // DE, AT, CH (no change here)

9. Set Methods compared directly against workarounds

Before the JavaScript Set Methods were available, developers had to implement set operations manually. The following comparisons show how much shorter, more readable and more efficient the new code is. The improvement in readability and intent is at least as valuable as the performance gains.

Operation Old workaround New Set Method Complexity
Union new Set([...a, ...b]) a.union(b) O(n+m) to O(n+m)
Intersection new Set([...a].filter(x => b.has(x))) a.intersection(b) O(n) to O(min(n,m))
Difference new Set([...a].filter(x => !b.has(x))) a.difference(b) O(n) to O(n)
Subset? [...a].every(x => b.has(x)) a.isSubsetOf(b) O(n) to O(n)
Disjoint? [...a].every(x => !b.has(x)) a.isDisjointFrom(b) O(n) to O(min(n,m))

Notably, all JavaScript Set Methods accept not only exact Set instances but also arbitrary set-like objects. The protocol requires a size property and a has() method as well as a keys() iterator. This means custom collection classes and even Map objects can be passed as arguments, as long as they implement the protocol. This design decision makes the Set Methods considerably more flexible than a pure set-to-set API.

Mironsoft

Modern JavaScript, performance optimization and scalable frontend architectures

Time to bring your JavaScript code up to modern standards?

We modernize existing JavaScript codebases, replace outdated workarounds with new APIs like Set Methods, and improve performance and readability sustainably.

Code modernization

Replace outdated array workarounds with Set Methods, Map APIs and modern iterator protocols

Performance review

Identify O(n²) hotspots in data processing pipelines and resolve them with native data structures

Training

Team workshops on modern JavaScript, ES2023 to ES2025 features with concrete use cases

10. Summary

The JavaScript Set Methods, union(), intersection(), difference(), symmetricDifference(), isSubsetOf(), isSupersetOf() and isDisjointFrom(), close a long-standing gap in the JavaScript standard library. Set operations that were previously implemented with error-prone array workarounds or external libraries are now directly available on the Set object. The non-mutating API always returns new sets and enables method chains that express complex logic in readable, declarative expressions.

Browser support has been complete across all modern browsers since 2024. Anyone needing to support older browsers can use the core-js polyfill or their own shim implementation offering the same API surface. For new projects and code modernizations, the Set Methods are the first choice for any logic that works with sets of unique values, from permission systems through tag filters to feature flag diffs.

JavaScript Set Methods, the essentials at a glance

New set methods

union(), intersection(), difference(), symmetricDifference(), all return a new set, are non-mutating and support method chaining.

Boolean check methods

isSubsetOf(), isSupersetOf(), isDisjointFrom(), for validations and guards without array conversion.

Performance

intersection() is O(min(n,m)) instead of O(n²) for the array workaround. No intermediate conversions between set and array needed.

Browser support

Chrome 122+, Firefox 127+, Safari 17+. Polyfill via core-js for older browsers. Accepts arbitrary set-like objects with has() and size.

11. FAQ: JavaScript Set Methods

1Are Set Methods mutating?
No. All Set Methods return a new set and leave the originals unchanged. This makes method chains safe and follows modern immutability principles.
2Which browsers support Set Methods?
Chrome 122+, Firefox 127+, Safari 17+, Edge 122+. Full support since 2024. Polyfill via core-js for older browsers.
3Only Set instances as arguments?
No. Arbitrary set-like objects with size, has() and keys() are accepted. Custom collection classes and Map objects also work as arguments.
4difference() vs. symmetricDifference()?
difference() returns A minus B, elements in A that are not in B. symmetricDifference() returns (A minus B) plus (B minus A), all elements that occur in exactly one of the sets.
5Why is intersection() more efficient?
The native implementation iterates over the smaller of the two sets, O(min(n,m)). The array workaround always iterates over the first array, even when it is much larger.
6Pass a Map as an argument?
Yes. Map implements the set-like protocol with size and has(). Map.keys() provides the required iterator. The caller must be a Set; the argument can be a Map or a custom collection.
7Polyfill for older browsers?
import 'core-js/proposals/set-methods-v2' or the npm package set-methods-polyfill. TypeScript: set lib in tsconfig.json to 'ES2025' or 'ESNext'.
8Chain Set Methods together?
Yes. a.union(b).intersection(c).difference(d) is valid. Every step creates a new set, which can be relevant for memory with very large sets and many chained steps.
9What does isDisjointFrom() return?
true when there are no common elements, meaning the intersection would be empty. false when at least one common element exists. Useful for conflict checks.
10What TC39 stage do the Set Methods have?
Stage 4, part of the ECMAScript 2025 standard. No longer a proposal but a standardized part of the JavaScript language specification.