JavaScript Set Methods: Union, Intersection, Difference
JavaScript's Set object now has built-in union, intersection, difference, and subset methods — replacing manual loops for combining collections.
JavaScript’s built-in Set object now ships native methods for combining and comparing sets — union(), intersection(), difference(), symmetricDifference(), and the boolean checks isSubsetOf(), isSupersetOf(), and isDisjointFrom(). Before these existed, every one of these operations meant converting sets to arrays, looping, and rebuilding a Set by hand. Now they’re one method call.
What a Set already gave you
A Set stores unique values with fast membership checks:
const a = new Set([1, 2, 3]);
a.has(2); // true
a.add(4);
a.size; // 4
That part hasn’t changed. What’s new is the vocabulary for combining two sets — operations borrowed directly from set theory in mathematics.
The new methods
const a = new Set([1, 2, 3, 4]);
const b = new Set([3, 4, 5, 6]);
a.union(b); // Set {1, 2, 3, 4, 5, 6}
a.intersection(b); // Set {3, 4}
a.difference(b); // Set {1, 2} (in a, not in b)
b.difference(a); // Set {5, 6} (in b, not in a)
a.symmetricDifference(b);// Set {1, 2, 5, 6} (in exactly one)
And the boolean comparisons:
new Set([1, 2]).isSubsetOf(a); // true — every element of {1,2} is in a
a.isSupersetOf(new Set([1, 2])); // true — a contains every element of {1,2}
new Set([9]).isDisjointFrom(a); // true — no elements in common
Every method accepts any set-like object — not just a Set instance — as long as it exposes a size property and has()/keys() methods, which is enough for these to interoperate with custom collection classes.
Before: the manual version
This is the pattern these methods replace:
// intersection, the old way
const intersection = new Set([...a].filter((x) => b.has(x)));
// union, the old way
const union = new Set([...a, ...b]);
// difference, the old way
const difference = new Set([...a].filter((x) => !b.has(x)));
Union was always reasonably concise via spread. Intersection and difference required a filter and a manual re-wrap into Set, and it was easy to get the order backwards (a.filter(x => b.has(x)) vs b.filter(x => a.has(x)) — these are not the same operation for difference). The named methods remove that ambiguity entirely: a.difference(b) unambiguously means “in a, not in b.”
Where this actually comes up
Set operations show up constantly in UI state, not just algorithms homework:
- Permission checks.
userRoles.isSupersetOf(requiredRoles)reads more directly than a manualevery()loop over an array. - Diffing selections. In a multi-select UI,
newSelection.symmetricDifference(oldSelection)gives you exactly the items that were added or removed — useful for firing minimal update events instead of re-rendering everything. - Tag filtering.
postTags.intersection(activeFilters).size > 0is a clean way to check whether a post matches any active filter tag. - Deduplicating across sources.
union()across sets pulled from multiple API responses collapses duplicates in one line, without the array-spread-then-new Set()dance.
A note on mutation
None of these methods mutate the receiver — a.union(b) returns a new Set and leaves a and b untouched, consistent with how Array.prototype.map and filter behave rather than how Array.prototype.sort used to. If you’re coming from Object.freeze habits or immutable-data patterns elsewhere in your codebase, these methods fit right in.
Feature support
Because this is a relatively recent JavaScript addition, check your target runtime’s support before relying on it in code that needs to run in older browsers or Node versions — Node’s release notes and the usual browser compatibility tables are the place to verify, rather than assuming. If you need to support older environments, a small polyfill or the manual filter-based patterns above cover the gap without changing behavior.
The takeaway
Set.prototype.union, intersection, difference, symmetricDifference, and the three isSubsetOf/isSupersetOf/isDisjointFrom checks turn a handful of filter-and-Set-rebuild idioms into single, unambiguous method calls. They don’t mutate their receiver, they accept any set-like object, and they read closer to the actual intent — reach for them anywhere you’re currently spreading two collections into arrays just to compare or combine them.
Tagged
Keep reading
Takina · · 4 min read Web Locks API: Coordinating Work Across Browser Tabs
The Web Locks API lets JavaScript acquire named locks shared across tabs, so only one tab does work like a token refresh or a write at a time.
Takina · · 4 min read Svelte 5 Runes Explained
Svelte 5 runes like $state and $derived replace the old reactive-assignment magic with explicit function calls that work anywhere in a file.
Takina · · 4 min read Shallow Copy vs Deep Copy in JavaScript
A shallow copy duplicates an object's top-level properties but shares nested references; a deep copy duplicates everything recursively. How to do each.