JavaScript basicsSets in plain wordsOne tool at a time
Set
Toolbox
A Set is a collection where every value appears only once, like a guest list that ignores repeat names. This guide shows how to fill one, remove duplicates, loop through it and combine sets with union and intersection, with a small example for each that you can run.
Key terms in plain words
Never written code before? Start here. Every word below turns up later on this page, each with an everyday comparison drawn from a party guest list, where every name appears once. Underlined words in the modules link back to these cards.
21 terms in 5 groups. Hover an underlined word anywhere on the page for a quick reminder, or click it to jump here.
What a Set is
A Set works like a guest list at the door. Each name is on it once, and the only question that matters is whether someone is on it.
- Set
- Think of it as a guest list that never lets the same name in twice
- A collection of values with no repeats. It has no numbered positions, so instead of asking what is at number 2 you ask whether a value is in it, and it answers very fast.
- For example
new Set(["js", "node", "js"])holds two values, not three. - Value
- Think of it as one name on the guest list
- A single thing stored in the set: a number, a piece of text, or an object. Each value appears at most once.
- For example In
new Set(["asha", "ravi"])the values are "asha" and "ravi". - Array
- Think of it as a numbered sign-in sheet where people can sign more than once
- An ordered list of values, written in square brackets. It allows repeats and has positions, and it carries tools like
map,filterandsortthat a Set lacks. - For example
["home", "cart", "home"]is an array with a repeat. - Duplicate
- Think of it as the same guest trying to sign in a second time
- A value that appears more than once. A Set quietly ignores duplicates, which makes removing them from an array a one line job.
- For example
[...new Set(visits)]gives each page once. - Size
- Think of it as the head count at the door
- How many values the set holds. It is a property, so you read it with no brackets.
- For example
tags.sizegives 3. - Property
- Think of it as a fact written at the top of the list, such as the total
- A value you read from something without calling it, so there are no brackets after it.
sizeis the Set's one property.
Adding, checking, removing
Writing a name on, checking someone at the door, and crossing a name off.
- Method
- Think of it as a standard thing you can ask the door staff to do
- A built-in action that belongs to the set, written with a dot and brackets after its name.
add,hasanddeleteare all methods. - For example
tags.has("node")calls the has method. - True or false
- Think of it as a nod or a shake of the head at the door
- The answer to a yes or no question.
hasgives one back, and so doesdelete, which tells you whether there was anything to remove. - For example
online.delete("zoya")givesfalse: she was not there. - Reference
- Think of it as two different guests who happen to share a name
- A set compares objects by identity, meaning "is this the very same object?", not by what is written inside. Two objects with the same contents are still two values.
- For example
new Set([{ id: 1 }, { id: 1 }]).sizeis 2. - Object
- Think of it as a guest with a name badge, a ticket and a plus one
- A bundle of named values, written in curly braces. A set can hold objects, but it tells them apart by identity rather than by their contents.
- For example
{ id: 42 }is a small object.
Walking through it
Reading the list from top to bottom, or copying it onto a sheet you can sort.
- Insertion order
- Think of it as names listed in the order guests arrived, not A to Z
- A set gives its values back in the order you added them, oldest first. It has no positions, but it does keep that order.
- Loop (for...of)
- Think of it as reading down the list one name at a time
- Repeating the same step for every value.
for...ofhands you each value in turn, in the order they were added. - For example
for (const colour of colours) { ... } - Callback
- Think of it as a note of instructions for whoever reads out each name
- A function you pass to a method so it can run it for you, once per value. This page calls it the helper. A set's
forEachpasses the value twice, to match the shape Map uses. - Spread syntax
- Think of it as copying every name onto a fresh numbered sheet
- Three dots,
[...set], which lays the values out one by one inside a new array. It is how you getmap,filterandsortback. - For example
[...colours].map((c) => c.toUpperCase())
Comparing two sets
Two guest lists side by side: who is on either, who is on both, who is on only one.
- Union
- Think of it as merging two guest lists into one, each name written once
- A new set holding every value that is in either set. Nobody appears twice, even if they were on both lists.
- For example
frontend.union(backend)gives all four people. - Intersection
- Think of it as the guests invited to both parties
- A new set holding only the values that appear in both sets.
- For example
frontend.intersection(backend)gives the people on both teams. - Difference
- Think of it as the guests on your list who are not on mine
- A new set holding what the first set has and the second does not. Order matters:
a.difference(b)is not the same asb.difference(a). The symmetric difference is everyone on exactly one of the two lists. - For example
frontend.difference(backend)gives frontend only. - Subset and superset
- Think of it as checking that every VIP is also on the main guest list
- Set A is a subset of B when everything in A is also in B; B is then a superset of A. Two sets are disjoint when they share nothing at all.
- For example
leads.isSubsetOf(frontend)istrue.
Memory and real use
Ticking guests off without holding on to them after they leave.
- WeakSet
- Think of it as a pencil tick beside a name that fades once the guest has gone
- A set that only holds objects and holds them loosely. Once nothing else in your program uses an object, it can drop out by itself. It has no size and cannot be looped over, just
add,hasanddelete. - Memory leak
- Think of it as a guest list that keeps every name forever, so the clipboard never stops growing
- When a program holds on to things it no longer needs, so the memory they use is never given back. A WeakSet avoids it for marks like "already handled".
- Permission
- Think of it as a wristband that says which rooms a guest may enter
- Something a person is allowed to do, such as read or write. In module 08 each role is a set of permissions, and set maths combines and checks them.
Make and read
Create a Set, add values, and check whether a value is in it.
A Set is like an array that refuses repeats. Add the same value twice and it keeps only one. It has no positions, so you do not ask "what is at number 2". You ask "is this value in here?", and it answers very fast.
Picture it: add
"js""node""js"new Set(...)"js""node"The repeat is dropped.Makes an empty set, or one filled from an array or other list.
See the exampleAdds the value if it is not already there.
See the exampleChecks if the value is in the set.
See the exampleconst tags = new Set();
tags.add("js");
tags.add("node").add("ts"); // add gives the set back, so you can chain
tags.add("js"); // already there: quietly ignored
console.log(tags.size); // 3, not 4
console.log(tags.has("node"), tags.has("go"));
console.log(tags);
Easy way to remember: add never complains about repeats, it just ignores them. That is the whole point of a Set.
$ node make.js 3 true false Set(3) { 'js', 'node', 'ts' }
Remove duplicates
Clean repeats out of any array in one line, and count how many different values there are.
const visits = ["home", "cart", "home", "pay", "cart", "home"];
const unique = [...new Set(visits)]; // the one line dedupe
console.log(unique);
console.log(new Set(visits).size, "different pages");
// works for letters too
console.log([...new Set("mississippi")].join(""));
// objects are compared by identity, not by what is inside
const a = { id: 1 };
console.log(new Set([a, a, { id: 1 }]).size);
Watch for this: two objects with the same contents are still two different objects, so a Set keeps both. It only drops the exact same object.
$ node dedupe.js [ 'home', 'cart', 'pay' ] 3 different pages misp 2
This is the most common reason people reach for a Set. Spread it back with [...set] when you need an array again.
Change and remove
Take one value out, or empty the set completely.
Removes every value at once.
See the exampleconst online = new Set(["asha", "ravi", "meera"]);
console.log(online.delete("ravi")); // true: found and removed
console.log(online.delete("zoya")); // false: was not there
console.log(online);
online.clear(); // remove everything
console.log(online.size);
Easy way to remember: delete tells you whether it found the value, so you can use its answer in an if.
$ node change.js true false Set(2) { 'asha', 'meera' } 0
Loop through
Walk through every value in the order you added them, or turn the set into an array.
Runs your helper on every value. It passes the value twice.
See the exampleconst colours = new Set(["red", "amber", "violet"]);
for (const colour of colours) { // in the order they were added
console.log(colour);
}
// forEach gives the value twice, to match the Map shape
colours.forEach((value, sameValue) => console.log(value === sameValue));
console.log([...colours].map((c) => c.toUpperCase())); // spread for array tools
Why it matters: a Set has no map or filter of its own. Spread it into an array first, then use the array tools you know.
$ node loop.js red amber violet true true true [ 'RED', 'AMBER', 'VIOLET' ]
Set maths
Combine two sets: everything in either, only what they share, or what one has that the other does not.
| Method | Gives you | Frontend + backend teams |
|---|---|---|
a.union(b) | everything in either set | all four people |
a.intersection(b) | only what is in both | people on both teams |
a.difference(b) | in a but not in b | frontend only |
a.symmetricDifference(b) | in exactly one of them | not on both |
a.isSubsetOf(b) | true if all of a is in b | yes or no |
a.isSupersetOf(b) | true if all of b is in a | yes or no |
a.isDisjointFrom(b) | true if they share nothing | yes or no |
const frontend = new Set(["asha", "ravi", "meera"]);
const backend = new Set(["meera", "kiran", "asha"]);
console.log(frontend.union(backend)); // in either team
console.log(frontend.intersection(backend)); // in both teams
console.log(frontend.difference(backend)); // frontend only
console.log(frontend.symmetricDifference(backend)); // in exactly one team
const leads = new Set(["asha"]);
console.log(leads.isSubsetOf(frontend), frontend.isSupersetOf(leads));
console.log(leads.isDisjointFrom(new Set(["kiran"])));
Needs: Node 22, Chrome 122, Firefox 127 or Safari 17 and newer. Every method gives a new set and leaves both originals alone.
$ node maths.js Set(4) { 'asha', 'ravi', 'meera', 'kiran' } Set(2) { 'asha', 'meera' } Set(1) { 'ravi' } Set(2) { 'ravi', 'kiran' } true true true
// the same answers on older engines, using filter
const a = new Set([1, 2, 3, 4]);
const b = new Set([3, 4, 5]);
const union = new Set([...a, ...b]);
const both = new Set([...a].filter((x) => b.has(x)));
const onlyA = new Set([...a].filter((x) => !b.has(x)));
console.log(union, both, onlyA);
For older engines: spread and filter give the same answers. It is slower on big sets, but works everywhere.
$ node older.js Set(5) { 1, 2, 3, 4, 5 } Set(2) { 3, 4 } Set(2) { 1, 2 }
Set or array?
Know when a Set is the better choice than an array, and when it is not.
| Question | Array | Set |
|---|---|---|
| Can values repeat? | Yes | No |
| Can I read by position? | Yes list[2] | No |
| Is "is it in here?" fast on big lists? | No checks one by one | Yes |
| Keeps the order you added? | Yes | Yes |
| Has map, filter, sort? | Yes | No spread first |
| Works with JSON? | Yes | No convert with [...set] |
| Best for | ordered lists and positions | unique values and fast lookups |
WeakSet
Mark objects as seen or done without keeping them in memory forever.
A WeakSet only holds objects, and holds them loosely. Once nothing else in your program uses an object, it can drop out by itself. It has no size and no looping, just add, has and delete.
// a WeakSet marks objects without keeping them alive
const processed = new WeakSet();
function handle(order) {
if (processed.has(order)) return `order ${order.id} already done`;
processed.add(order);
return `processing order ${order.id}`;
}
const order = { id: 42 };
console.log(handle(order));
console.log(handle(order)); // the same object is recognised
try { processed.add("42"); } catch (err) { console.log(err.constructor.name); }
console.log(typeof processed.size); // no size and no looping
When to reach for it: tracking which requests, orders or page elements you have already handled, without causing a memory leak.
$ node weak.js processing order 42 order 42 already done TypeError undefined
All together
Combine role permissions for a user and check what an action needs.
A person can have several roles, and each role grants a few permissions. Sets make this neat: union joins the permissions, isSubsetOf checks an action, and difference lists what is missing.
// who can do what: combine role permissions, then check what an action needs
const roles = {
viewer: new Set(["read"]),
editor: new Set(["read", "write"]),
billing: new Set(["read", "invoice"]),
};
const permsFor = (...names) => names.reduce((all, n) => all.union(roles[n]), new Set());
const shree = permsFor("editor", "billing");
console.log([...shree]);
const can = (perms, ...needs) => new Set(needs).isSubsetOf(perms);
console.log(can(shree, "write", "invoice"));
console.log(can(permsFor("viewer"), "write"));
const missing = new Set(["read", "delete"]).difference(shree);
console.log("missing:", [...missing].join(", "));
What each part uses: new Set from module 01, union, isSubsetOf and difference from 05, and spread from 04 to print the result.
$ node access.js [ 'read', 'write', 'invoice' ] true false missing: delete
# save any example as a file and run it with Node 22 $ node access.js # or paste it into your browser console with F12
Which one do I need?
Start from what you want to do, then read across to the tool and what it gives back.
| I want to | Use | You get |
|---|---|---|
| Make a set | new Set() | an empty set |
| Remove repeats from an array | [...new Set(list)] | a unique array |
| Add a value | set.add(v) | the same set |
| Check a value | set.has(v) | true or false |
| Count values | set.size | a number |
| Remove a value | set.delete(v) | true or false |
| Loop over it | for (const v of set) | every value, in order |
| Everything in either | a.union(b) | a new set |
| Only what both share | a.intersection(b) | a new set |
| In a but not b | a.difference(b) | a new set |
| All of a inside b? | a.isSubsetOf(b) | true or false |
| Mark objects without leaks | new WeakSet() | a weak set |