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.
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 exampleCounts the values. A property, so no brackets.
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 that value.
See the exampleRemoves 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.
Visits every value, oldest first.
See the exampleRuns your helper on every value. It passes the value twice.
See the exampleSpreads into an array so you can map, filter or sort.
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 |