Set operations
Use the ES2025 Set methods for union, intersection, difference, relationship checks, and set-like inputs without mutating your original data.
- 01Combine unique values clearlyUse union, intersection, difference, and symmetricDifference instead of hand-written array filters.
- 02Ask relationship questionsCheck subsets, supersets, and disjoint sets for permissions, tags, and matching features.
- 03Recognize set-like inputsKnow why Maps work, custom set-like objects work, and arrays must be wrapped in new Set().
Sets as friend groups
A Set already gives you uniqueness: a value can appear once, no matter how many times you try to add it. ES2025 adds the missing vocabulary for comparing two Sets: union, intersection, difference, symmetric difference, and three true/false relationship checks.
The curriculum summary for this lesson is simple: union, intersection, and difference with the built-in Set methods. The important upgrade is clarity. Instead of translating a question into spreads, filter, and includes, you can write the question you mean.
Imagine two friend groups arriving at a party. You can ask, “Who is in either group?”, “Who is in both?”, “Who is only with us?”, or “Who is not shared?” Those are set operations. The new methods make the questions read like English.
- In real life: Everyone either group invited
- In JavaScript:
a.union(b) - In real life: Friends both groups know
- In JavaScript:
a.intersection(b) - In real life: People only my group knows
- In JavaScript:
a.difference(b) - In real life: Everyone except the shared friends
- In JavaScript:
a.symmetricDifference(b)
Where the analogy stops: Friend groups are fuzzy in real life. JavaScript Sets are exact: membership is tested with the same equality rules Set already uses, so objects only match when they are the same object reference.
These are ES2025 methods, available in Node 22 and modern browsers. The playground below feature-detects them after the page mounts. If a browser does not support them yet, use a manual fallback with arrays and filter/includes, or a vetted polyfill.
This lesson builds on Map & Set for Set basics, on Searching arrays for the old filter/includes style, and on map/filter/reduce for list transformations.
Union & intersection
INTERACTIVEa.union(b) creates a new Set containing values from a, then values from b that are not already present. That order rule matters when you turn the result back into an array: the caller’s insertion order comes first, then the argument contributes only new values.
a.intersection(b) creates a new Set containing values that appear in both. For ordinary Sets of the same size, you can think “walk the left Set and keep matches.” The specification may iterate the smaller side for efficiency, but the lesson examples use small Sets where the visible result is stable and tested.
const devA = new Set(["JavaScript","CSS","Accessibility"]);const devB = new Set(["CSS","React","Testing"]);const result = [...devA.union(devB)];console.log(result);Developer A
Developer B
[JavaScript, CSS, Accessibility, React, Testing][JavaScript, CSS, Accessibility][CSS, React, Testing]Checking this browser after mount so the server and browser render the same page.
The Venn diagram is labeled as a diagram because JavaScript is not drawing circles internally. The result list beside it is real: it comes from the native method shown in the code pane. Notice that the original Sets stay unchanged after every operation.
Step through four operations. Each result is a real Set produced by the native ES2025 methods.
script
const yourFriends = new Set(["Bea", "Cal", "Dev"]);myFriends.union(yourFriends);myFriends.intersection(yourFriends);myFriends.difference(yourFriends);myFriends.symmetricDifference(yourFriends);Difference & symmetricDifference
STEP THROUGHdifference is directional. oldTags.difference(newTags) means “what was removed?” newTags.difference(oldTags) means “what was added?” Same two Sets, different caller, different question.
symmetricDifference is the “changed on either side” operation: values that appear in exactly one Set. It is great for summaries, but less precise than running difference both ways when you need separate added and removed lists.
Use the setting to swap which Set calls the method. Difference is the easiest place to see why direction matters.
script
const newTags = new Set(["js", "react", "accessibility"]);oldTags.difference(newTags);oldTags.isSupersetOf(newTags);oldTags.isDisjointFrom(newTags);Compare what was in your backpack before and after a trip. The direction tells you whether you lost something or added something. If you only care that the packing list changed, symmetric difference gives one combined list.
- In real life: Packed before but not after
- In JavaScript:
before.difference(after)finds removed items - In real life: Packed after but not before
- In JavaScript:
after.difference(before)finds added items - In real life: Changed either way
- In JavaScript:
before.symmetricDifference(after)
Where the analogy stops: A backpack can have two identical socks. A Set cannot store duplicates, so these methods answer membership questions, not quantity questions.
isSubsetOf, isSupersetOf & isDisjointFrom
The three predicate methods return booleans. Use them when the answer should be true or false, not a new collection.
small.isSubsetOf(big): every value insmallis also inbig.big.isSupersetOf(small):bigcontains every value fromsmall.a.isDisjointFrom(b): the Sets share no values at all.
A book club can be fully inside a school roster. That same fact can be said from either side: the club is a subset of the school, and the school is a superset of the club. Two sports teams are disjoint only if no student appears on both rosters.
- In real life: Every book club member attends the school
- In JavaScript:
bookClub.isSubsetOf(school) - In real life: The school contains the whole book club
- In JavaScript:
school.isSupersetOf(bookClub) - In real life: Two teams share nobody
- In JavaScript:
teamA.isDisjointFrom(teamB)
Where the analogy stops: The method only checks membership. It does not explain which value made the answer false; use difference when you need that report.
const required = new Set(["read", "write"]);
const user = new Set(["read", "write", "delete"]);
if (required.isSubsetOf(user)) {
console.log("allowed");
}The caller communicates intent. “Are all required permissions inside the user’s permissions?” reads clearly as required.isSubsetOf(user). The equivalent user.isSupersetOf(required) is also correct; choose the one that matches your sentence.
Set-like objects
STEP THROUGHThe argument to these methods does not have to be an actual Set. It must be set-like: an object with a numeric size, a has(value) method, and a keys() method that produces its values. A Map qualifies because its keys act like the set of values.
Set methods accept set-like objects, not every iterable. Step through a Map, a custom object, and an array mistake.
script
const requiredMap = new Map([["read", true], ["write", true]]);userPermissions.isSupersetOf(requiredMap);const requiredLike = { size: 2, has(value) { return value === "read" || value === "write"; }, keys() { return ["read", "write"][Symbol.iterator](); },};userPermissions.isSupersetOf(requiredLike);userPermissions.union(["read"]);const userPermissions = new Set(["read", "write", "admin"]);const requiredMap = new Map([["read", true], ["write", true]]);userPermissions.isSupersetOf(requiredMap);const requiredLike = { size: 2, has(value) { return value === "read" || value === "write"; }, keys() { return ["read", "write"][Symbol.iterator](); },};userPermissions.isSupersetOf(requiredLike);userPermissions.union(["read"]);Arrays are iterable, but they are not set-like because arrays do not have has(value). Passing an array throws a TypeError. Write mySet.union(new Set(array)) when your data starts as an array.
The old way vs the new way
Before these methods, you already had the tools: spread a Set into an array, filter the other array, and check membership with has or includes. That still works and is the right fallback when you cannot rely on ES2025 support. The new methods are easier to read and harder to get subtly wrong.
const a = new Set(["js", "css", "html"]);const b = new Set(["css", "react"]);const newWay = [...a.union(b)];const oldWay = [...a, ...[...b].filter((item) => !a.has(item))];console.log(newWay.join(", "));console.log(oldWay.join(", "));console.log([...a].join(", "));Both styles print the same union, and line 7 proves the original caller is still js, css, html. These methods return new Sets; they do not mutate the originals.
| Method | Returns | Venn region | Example result |
|---|---|---|---|
a.union(b) | A new Set | Everything in either circle | ['Ada', 'Bea', 'Cal', 'Dev'] |
a.intersection(b) | A new Set | Only the overlap | ['Bea', 'Cal'] |
a.difference(b) | A new Set | Only the left circle | ['Ada'] |
a.symmetricDifference(b) | A new Set | Both outside-only regions | ['Ada', 'Dev'] |
a.isSubsetOf(b) | Boolean | Is the left circle fully inside the right? | false |
a.isSupersetOf(b) | Boolean | Does the left circle contain the right? | false |
a.isDisjointFrom(b) | Boolean | Do the circles never overlap? | false |
- Everyone invited by either friend group
- People both groups already know
- Tags that were removed from the old list
- Tags that appear on exactly one side
- Does this user have every required permission?
- Do these two teams share nobody?
- People only my friend group knows
- Does the school roster contain the whole book club?
Sort each real question by the kind of Set operation it needs. Directional cards are the ones where swapping caller and argument changes the meaning.
Where you'll use this
Set operations show up whenever lists represent membership: tags, permissions, selected filters, feature flags, shopping-list ingredients, or shared interests.
const before = new Set(["js", "css", "accessibility"]);
const after = new Set(["js", "react", "accessibility"]);
const added = after.difference(before); // Set { "react" }
const removed = before.difference(after); // Set { "css" }
const changed = before.symmetricDifference(after); // Set { "css", "react" }const mia = new Set(["music", "hiking", "games"]);
const noor = new Set(["cooking", "music", "games"]);
const shared = mia.intersection(noor); // Set { "music", "games" }A good habit: keep your domain sentence beside the code. “Added tags” is after.difference(before); “removed tags” is before.difference(after). That sentence prevents swapped arguments during maintenance.
Common misconceptions
“Union changes the first Set.”
No. union, intersection, difference, and symmetricDifference return new Sets.
“Difference works the same both ways.”
No. a.difference(b) means values only in a. Swap the caller and you ask a different question.
“Arrays count as set-like because they are iterable.”
No. Set-like means size, has, and keys. Wrap arrays in new Set(array).
“Intersection always reports in the caller’s order.”
Often in small examples, yes, but the spec can use the smaller collection for efficiency. Do not rely on intersection as a sorting tool.
“Set equality is deep equality.”
Objects match only when they are the same object reference. Two separate { id: 1 } objects are different Set values.
If you want to keep a result, name it: const added = after.difference(before). The original Sets are still available for other checks, which makes these methods friendly to immutable update patterns.
Practice exercises
5 EXERCISESPredict the two printed lines.
const a = new Set(["red", "blue", "green"]);
const b = new Set(["blue", "gold"]);
console.log([...a.union(b)].join(", "));
console.log([...a.intersection(b)].join(", "));The union is red, blue, green, gold; blue is not duplicated. The intersection is just blue.
Does the user have every required permission?
const required = new Set(["read", "write"]);
const user = new Set(["read", "write", "delete"]);
console.log(required.isSubsetOf(user));required.isSubsetOf(user) is true because both required values appear in the user's Set.
Type the added tag and the removed tag.
const before = new Set(["js", "css", "accessibility"]);
const after = new Set(["js", "react", "accessibility"]);
console.log([...after.difference(before)].join(", "));
console.log([...before.difference(after)].join(", "));after.difference(before) prints react; before.difference(after) prints css.
The starter code passes an array to union. Fix it by wrapping the array, then predict the output.
const favorites = new Set(["tea", "coffee"]);
const incoming = ["coffee", "juice"];
console.log([...favorites.union(incoming)].join(", "));const favorites = new Set(["tea", "coffee"]);
const incoming = ["coffee", "juice"];
console.log([...favorites.union(new Set(incoming))].join(", "));Arrays do not have has, so union(incoming) throws. union(new Set(incoming)) prints tea, coffee, juice.
Which recipe ingredients are already in the pantry?
const pantry = new Set(["flour", "eggs", "milk", "sugar"]);
const recipe = new Set(["eggs", "butter", "sugar"]);
console.log([...pantry.intersection(recipe)].join(", "));pantry.intersection(recipe) returns eggs and sugar, in the order they appear in the pantry Set.
Quiz: check your understanding
7 QUESTIONSQuestion 1 of 7What does
unionreturn?Choose an answer to see the explanation.
Question 2 of 7What does the difference snippet print?
Read the code, then predictconst a = new Set(["a", "b", "c"]); const b = new Set(["b", "d"]); console.log([...a.difference(b)].join(""));Choose an answer to see the explanation.
Question 3 of 7Which method answers: does the user have every required permission?
Choose an answer to see the explanation.
Question 4 of 7What does the subset snippet print?
Read the code, then predictconst school = new Set(["Mina", "Omar", "Pia"]); const bookClub = new Set(["Mina", "Pia"]); console.log(bookClub.isSubsetOf(school));Choose an answer to see the explanation.
Question 5 of 7Which argument is set-like for these Set methods?
Choose an answer to see the explanation.
Question 6 of 7What does the symmetric difference snippet print?
Read the code, then predictconst a = new Set(["JS", "CSS"]); const b = new Set(["CSS", "HTML"]); console.log([...a.symmetricDifference(b)].join(","));Choose an answer to see the explanation.
Question 7 of 7Do these methods mutate the original Sets?
Choose an answer to see the explanation.
Key takeaways
union,intersection,difference, andsymmetricDifferencereturn new Sets.differenceis directional; swap the caller to answer the opposite side.isSubsetOf,isSupersetOf, andisDisjointFromreturn booleans.- Set-like arguments need
size,has, andkeys. Maps work; arrays must be wrapped innew Set(array). - Feature-detect in browsers that may not have ES2025 Set methods yet.
Remember the one-liner.
Set operations let Sets answer membership questions directly: combine, compare, or ask relationships without mutating the originals.
Up next: WeakMap & WeakSet.