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.
Table of Contents
- 1. Why JavaScript went without Set Methods for so long
- 2. union(): the union of two sets
- 3. intersection(): computing the intersection
- 4. difference(): the difference set and what it means
- 5. symmetricDifference(): elements in exactly one set
- 6. isSubsetOf(), isSupersetOf(), isDisjointFrom()
- 7. Practical examples: filters, permissions, diff algorithms
- 8. Performance: Set Methods vs. array workarounds
- 9. Set Methods compared directly against workarounds
- 10. Summary
- 11. FAQ
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.