completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Set operations

Use the ES2025 Set methods for union, intersection, difference, relationship checks, and set-like inputs without mutating your original data.

By the end you can
  • 01
    Compare two Sets with one callUse union, intersection, difference, and symmetricDifference instead of building the answer by hand from arrays.
  • 02
    Ask yes-or-no questionsUse isSubsetOf, isSupersetOf, and isDisjointFrom to check permissions, tags, and shared values.
  • 03
    Know what you can pass inExplain why a Map or a home-made set-like object works, and why an array must be wrapped in new Set() first.

Sets as friend groups

A Set is a collection where each value appears only once. If you add the same value twice, the Set still holds it once.

Often you have two Sets and want to compare them. JavaScript's 2025 edition, called ES2025, added seven methods for this. Four of them make a new Set:

  • union: every value from either Set.
  • intersection: only the values both Sets have.
  • difference: the values only the first Set has.
  • symmetricDifference: the values only one of the two Sets has.

The other three answer a yes-or-no question with true or false. You'll meet them in their own section.

Before these methods, you had to build each answer by hand from arrays, filter, and includes. Now the method name says what you want.

Real-life analogyTwo friend groups at a party

Two friend groups arrive at a party. You can ask four questions. Who is in either group? Who is in both groups? Who is only with my group? Who is in just one of the groups? Each question is one of the four methods.

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)
Which browsers have these methods

These methods are new. Node 22 and up-to-date browsers have them. An older browser may not. The playground below checks for them once the page has loaded. If they are missing, use the older way shown later in this lesson, with arrays and filter/includes. Or load a polyfill: a trusted script that adds a missing feature.

Join two Sets with unionPop out in the code editor (opens in a new tab)JavaScript
const cart = new Set(["tea", "bread"]);
const newItems = new Set(["bread", "eggs"]);
const allItems = cart.union(newItems);
console.log([...allItems].join(", "));

Line 1 makes a Set called cart with two groceries. Line 2 makes a Set of new items. One of them, bread, is already in the cart. Line 3 calls union. It makes a new Set with the values of both. Line 4 prints that new Set. The spread [...allItems] copies the values into an array so join can put commas between them.

It prints tea, bread, eggs. The cart's values come first, in their order. Then come the new values. bread appears only once.

Two Sets. cart holds tea and bread. newItems holds bread and eggs. cart.union(newItems) makes a new Set, allItems, that holds tea, bread, and eggs. bread is there only once.teabreadcartbreadeggsnewItemscart.union(newItems)teabreadeggsallItemsbread only once
union puts the values of both Sets into a new Set. bread is in both, but the new Set holds it only once. cart and newItems stay as they were.
Check that the browser has the methodsPop out in the code editor (opens in a new tab)JavaScript
const hasSetMethods = typeof Set.prototype.union === "function";
console.log(hasSetMethods);

Line 1 asks whether Set.prototype.union is a function. Set.prototype is the place where all Sets find their methods, and typeof tells you what kind of value something is. A browser that has the method prints true.

This kind of check is called feature detection. It is safer than guessing what a browser supports. If the check gives false, use the older way shown later.

This lesson uses ideas from earlier lessons:

  • Map & Set, for how a Set works.
  • Searching arrays, for the older filter/includes way.
  • map/filter/reduce, for turning one list into another.

Union & intersection

INTERACTIVE

In a.union(b), two Sets take part. This lesson gives them names:

  • The caller is the Set before the dot, a. It calls the method.
  • The argument is the Set inside the brackets, b.

a.union(b) makes a new Set. First it takes every value from the caller, in order. Then it adds the values from the argument that are not in it yet. So when you turn the result into an array, the caller's values come first.

a.intersection(b) makes a new Set with only the values that are in both Sets. A simple way to picture it: walk through the caller and keep each value the argument also has.

When the argument is the smaller Set, JavaScript may walk through the argument instead, to save time. Then the result can come out in a different order. So use intersection to find shared values, not to put values in order.

Compare two skill Sets
Run the chosen methodPop out in the code editor (opens in a new tab)JavaScript
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

Drawingunion
A onlyJavaScript, Accessibility
BothCSS
B onlyReact, Testing
New Set[JavaScript, CSS, Accessibility, React, Testing]
Original A[JavaScript, CSS, Accessibility]
Original B[CSS, React, Testing]
Try it yourself

Checking whether this browser has the new Set methods. The check waits until the page has loaded.

Click the skills to change what Developer A and Developer B know. Then pick a method. The circles are only a drawing; the result comes from the real Set method in the code.

The circles are a drawing to help you see the two groups. The result list next to them is real. It comes from the actual method in the code. The rows Original A and Original B show that the method never changes the two Sets it compares.

Step through the four methods that make a new Set
Step 0 of 6Ready
Your turn: follow the blue line

Step through the four methods that make a new Set. Each result comes from the real method running in your browser.

Running in
  1. script
Next: line 1
const yourFriends = new Set(["Bea", "Cal", "Dev"]);myFriends.union(yourFriends);myFriends.intersection(yourFriends);myFriends.difference(yourFriends);myFriends.symmetricDifference(yourFriends);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Difference & symmetricDifference

STEP THROUGH

a.difference(b) keeps the values that are in the caller, a, but not in the argument, b. So which Set comes first matters.

  • oldTags.difference(newTags) answers "which tags were removed?"
  • newTags.difference(oldTags) answers "which tags were added?"

The two Sets are the same in both lines. Only the caller changes, and so does the question.

Two lists of tags side by side. oldTags holds js, css, accessibility. newTags holds js, react, accessibility. js and accessibility are in both. css is only in oldTags, so it was removed. react is only in newTags, so it was added.oldTagsnewTagsjsbothjscssremovedreactaddedaccessibilitybothaccessibilityoldTags.difference(newTags) → cssnewTags.difference(oldTags) → react
js and accessibility are in both Sets, so difference drops them. The Set before the dot decides which side you keep: css from the old tags, or react from the new tags.

symmetricDifference keeps the values that are in exactly one of the two Sets. It drops the values both Sets share. Use it when you only want one list of "what changed". When you need the added values and the removed values as two separate lists, call difference twice, once each way.

Swap which Set comes first
Step 0 of 5Ready
Your turn: follow the blue line

Use the setting to choose which Set comes before the dot. Watch line 3: difference gives a different answer each way.

Running in
  1. script
Next: line 1
const newTags = new Set(["js", "react", "accessibility"]);oldTags.difference(newTags);oldTags.isSupersetOf(newTags);oldTags.isDisjointFrom(newTags);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Which Set comes before the dot?

Changing this starts the replay again. Watch line 3: it now asks a different question.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Real-life analogyA checklist before and after a trip

You write down what is in your backpack before a trip, and again after it. Compare the lists one way, and you find what you lost. Compare them the other way, and you find what you picked up. If you only want to know what changed, you make one list of both.

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)

isSubsetOf, isSupersetOf & isDisjointFrom

These three methods don't make a new Set. They give back a boolean: true or false. Use them when your question has a yes-or-no answer.

  • small.isSubsetOf(big): is every value in small also in big?
  • big.isSupersetOf(small): does big have every value that small has?
  • a.isDisjointFrom(b): do the two Sets share no values at all?
Three yes-or-no questionsPop out in the code editor (opens in a new tab)JavaScript
const required = new Set(["read", "write"]);
const user = new Set(["read", "write", "delete"]);
console.log(required.isSubsetOf(user));
console.log(user.isSupersetOf(required));
console.log(required.isDisjointFrom(user));

Line 1 lists the permissions a page needs. Line 2 lists the permissions a user has. Lines 3 and 4 ask the same question from opposite sides: does the user have everything required? Both print true.

Line 5 asks whether the two Sets share nothing. They share read and write, so it prints false.

Two Sets. required holds read and write. user holds read, write, and delete. Both values in required are also in user, so required.isSubsetOf(user) is true, and user.isSupersetOf(required) is true too.readwriterequiredall foundreadwritedeleteuserrequired.isSubsetOf(user) → trueuser.isSupersetOf(required) → true
Every value in required is also in user. So required fits inside user, and both questions answer true. The extra delete in user doesn't matter.

These methods only say yes or no. When a check gives false and you want to know which values caused it, use difference.

Real-life analogyBook club inside the school roster

Every member of the book club is a student at the school. You can say this two ways. The club fits inside the school. The school holds the whole club. Two sports teams are separate only if no student plays on both.

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)
Allow the user only with every permissionPop out in the code editor (opens in a new tab)JavaScript
const required = new Set(["read", "write"]);
const user = new Set(["read", "write", "delete"]);

if (required.isSubsetOf(user)) {
  console.log("allowed");
}

Line 4 reads like the question you are asking: are all the required permissions inside the user's permissions? Here they are, so line 5 prints allowed. Writing user.isSupersetOf(required) gives the same answer. Pick the one that matches how you say the question.

A Map as the argumentPop out in the code editor (opens in a new tab)JavaScript
const user = new Set(["read", "write"]);
const required = new Map([["read", true]]);
console.log(user.isSupersetOf(required));

A Map holds pairs of a key and a value. When a Map is the argument, the method looks only at its keys. The value true is ignored. The only key is read, and user has read, so line 3 prints true.

Set-like objects

STEP THROUGH

The argument doesn't have to be a real Set. It has to be set-like. A set-like object has these three things:

  • a size property that is a number,
  • a has(value) method that says whether a value is in it,
  • a keys() method that hands out its values one by one.

A Map has all three. Its keys play the part of the values.

A Map, a home-made set-like object, and an array mistake
Step 0 of 6Ready
Your turn: follow the blue line

These methods take set-like objects, not everything you can loop over. Step through a Map, a home-made object, and an array mistake.

Running in
  1. script
Next: line 1
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"]);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
The full code: a Map, a home-made object, and an arrayPop out in the code editor (opens in a new tab)JavaScript
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"]);

Lines 4 to 8 make a plain object with size, has, and keys. It is not a Set, but it has the three things, so line 9 works too.

You can loop over an array with for...of. Things you can loop over like that are called iterable. But an array is not set-like, because it has no has(value) method. So line 10 throws a TypeError. When your values start in an array, wrap it first: mySet.union(new Set(array)).

Left: a Map called requiredMap with the keys read and write. It has size, has(), and keys(), so it works as the argument. Right: an array with read in it. It has no has() method, so passing it throws a TypeError.a MapreadwriteMapsize, has(), keys()worksan array["read"]no has()TypeError
The method asks the argument for its size, has(), and keys(). A Map has all three, and its keys are the values. An array has no has(), so the call throws a TypeError.

The old way vs the new way

Before these methods, you built each answer by hand. You copied a Set into an array with spread, filtered the other one, and checked each value with has or includes. That still works. Use it when a browser doesn't have the new methods. The new methods are easier to read and easier to get right.

The same union, written two waysPop out in the code editor (opens in a new tab)JavaScript
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(", "));

Line 3 is the new way: one call to union. Line 4 is the old way. It copies every value of a, then adds each value of b that a doesn't have. Lines 5 and 6 print the same list.

Line 7 prints a again, and it is still js, css, html. These methods make new Sets. They never change the Sets you start with.

The table below lists all seven methods. Every row uses the two party groups: a holds Ada, Bea, and Cal, and b holds Bea, Cal, and Dev.

What each Set method gives back, for the two party groups
When you writeWhat you get backWhat it keeps or asksResult for the party groups
a.union(b)A new Set.It keeps every value from either Set.['Ada', 'Bea', 'Cal', 'Dev']
a.intersection(b)A new Set.It keeps only the values both Sets have.['Bea', 'Cal']
a.difference(b)A new Set.It keeps the values only a has.['Ada']
a.symmetricDifference(b)A new Set.It keeps the values only one of the two Sets has.['Ada', 'Dev']
a.isSubsetOf(b)true or false.It asks: is every value in a also in b?false, because Ada is not in b.
a.isSupersetOf(b)true or false.It asks: does a have every value in b?false, because Dev is not in a.
a.isDisjointFrom(b)true or false.It asks: do the two Sets share no values?false, because they share Bea and Cal.
Which operation answers this?
  • 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?
Try it yourself
0 of 8 correct

Sort each question by the kind of answer it needs. Put a card in “Order matters” when swapping the caller and the argument would change the question.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Where you'll use this

You'll use these methods whenever a list says who or what belongs to a group. For example:

  • tags on a blog post,
  • a user's permissions,
  • the filters someone ticked on a shopping site,
  • features switched on for a user,
  • ingredients in a recipe,
  • hobbies two people share.
Find the added and removed tagsPop out in the code editor (opens in a new tab)JavaScript
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" }
Find the hobbies two people sharePop out in the code editor (opens in a new tab)JavaScript
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: say the question in words, and let the variable name say it too. "Added tags" is after.difference(before). "Removed tags" is before.difference(after). A clear name stops someone from swapping the two Sets by mistake later.

Common misconceptions

“Union changes the first Set.”

No. union, intersection, difference, and symmetricDifference each make a new Set. The Sets you start with stay the same.

“Difference works the same both ways.”

No. a.difference(b) keeps the values only a has. Swap the two Sets and you ask a different question.

“Arrays count as set-like because you can loop over them.”

No. Set-like means it has size, has, and keys. Arrays have no has. Wrap an array first: new Set(array).

“Intersection always keeps the caller’s order.”

It often does in small examples. But when the argument is smaller, JavaScript may walk through the argument instead. Don't use intersection to put values in order.

“Two objects that look the same are the same Set value.”

No. An object matches only itself. Two separate { id: 1 } objects are two different values to a Set.

An object matches only itselfPop out in the code editor (opens in a new tab)JavaScript
const savedUser = { id: 1 };
const currentUser = { id: 1 };
const saved = new Set([savedUser]);
console.log(saved.has(savedUser));
console.log(saved.has(currentUser));

Lines 1 and 2 make two separate objects that look the same. Line 3 puts only the first one in a Set. Line 4 asks about that same object, so it prints true. Line 5 asks about the other object. It looks the same, but it is a different object, so it prints false.

A good habit

To keep a result, store it in a variable with a clear name: const added = after.difference(before). The two Sets you started with don't change, so you can still use them for other checks.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upPredict union and intersection

What do the two lines print?

Starter codePop out in the code editor (opens in a new tab)JavaScript
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(", "));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeCheck required permissions

    Does the user have every required permission? What does it print?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const required = new Set(["read", "write"]);
    const user = new Set(["read", "write", "delete"]);
    console.log(required.isSubsetOf(user));

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFind added and removed tags

      Type the added tag, then the removed tag.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      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(", "));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeFix passing an array to union

        The starter code passes an array to union. Fix it by wrapping the array in a Set. Then predict what it prints.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const favorites = new Set(["tea", "coffee"]);
        const incoming = ["coffee", "juice"];
        console.log([...favorites.union(incoming)].join(", "));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeFind the shared ingredients

          Which recipe ingredients are already in the pantry?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const pantry = new Set(["flour", "eggs", "milk", "sugar"]);
          const recipe = new Set(["eggs", "butter", "sugar"]);
          console.log([...pantry.intersection(recipe)].join(", "));

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            7 QUESTIONS
            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What does union return?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does the difference snippet print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const 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.

            3. Question 3 of 7Which call answers: does the user have every required permission?

              Choose an answer to see the explanation.

            4. Question 4 of 7What does the subset snippet print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const school = new Set(["Mina", "Omar", "Pia"]);
              const bookClub = new Set(["Mina", "Pia"]);
              console.log(bookClub.isSubsetOf(school));

              Choose an answer to see the explanation.

            5. Question 5 of 7Which argument is set-like for these Set methods?

              Choose an answer to see the explanation.

            6. Question 6 of 7What does the symmetric difference snippet print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const a = new Set(["JS", "CSS"]);
              const b = new Set(["CSS", "HTML"]);
              console.log([...a.symmetricDifference(b)].join(","));

              Choose an answer to see the explanation.

            7. Question 7 of 7Do these methods change the original Sets?

              Choose an answer to see the explanation.

            Key takeaways

            • union, intersection, difference, and symmetricDifference each make a new Set.
            • With difference, order matters. Swap the two Sets to get the other side.
            • isSubsetOf, isSupersetOf, and isDisjointFrom give back true or false.
            • The argument must be set-like: it needs size, has, and keys. A Map works. Wrap an array in new Set(array) first.
            • Older browsers may not have these methods. Check for them before you use them.

            Remember the one-liner.
            Set methods compare two Sets for you: join them, find what they share, find what one has, or answer yes or no, and the original Sets never change.

            Up next: WeakMap & WeakSet.

            CompleteFrontend Clear concepts. Working examples.