Set Toolbox 0/8

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.

JavaScript ES202516 tools
00

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
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
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
An ordered list of values, written in square brackets. It allows repeats and has positions, and it carries tools like map, filter and sort that a Set lacks.
For example ["home", "cart", "home"] is an array with a repeat.
Duplicate
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
How many values the set holds. It is a property, so you read it with no brackets.
For example tags.size gives 3.
Property
A value you read from something without calling it, so there are no brackets after it. size is the Set's one property.

Adding, checking, removing

Writing a name on, checking someone at the door, and crossing a name off.

Method
A built-in action that belongs to the set, written with a dot and brackets after its name. add, has and delete are all methods.
For example tags.has("node") calls the has method.
True or false
The answer to a yes or no question. has gives one back, and so does delete, which tells you whether there was anything to remove.
For example online.delete("zoya") gives false: she was not there.
Reference
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 }]).size is 2.
Object
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
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)
Repeating the same step for every value. for...of hands you each value in turn, in the order they were added.
For example for (const colour of colours) { ... }
Callback
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 forEach passes the value twice, to match the shape Map uses.
Spread syntax
Three dots, [...set], which lays the values out one by one inside a new array. It is how you get map, filter and sort back.
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
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
A new set holding only the values that appear in both sets.
For example frontend.intersection(backend) gives the people on both teams.
Difference
A new set holding what the first set has and the second does not. Order matters: a.difference(b) is not the same as b.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
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) is true.

Memory and real use

Ticking guests off without holding on to them after they leave.

WeakSet
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, has and delete.
Memory leak
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
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.
01

Make and read

Create a Set, add values, and check whether a value is in it.

Start herenew SetaddhasMust know

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.
new Set(list?)

Makes an empty set, or one filled from an array or other list.

See the example
Gives a new setMakes a new one
add(value)

Adds the value if it is not already there.

See the example
Gives the same setChanges it
has(value)

Checks if the value is in the set.

See the example
Gives true or falseOnly reads
size

Counts the values. A property, so no brackets.

See the example
Gives a numberOnly reads
make.jsJAVASCRIPT
const 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.

Terminal OUTPUT
$ node make.js
3
true false
Set(3) { 'js', 'node', 'ts' }
02

Remove duplicates

Clean repeats out of any array in one line, and count how many different values there are.

Most useddedupe[...new Set()]
dedupe.jsJAVASCRIPT
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.

Terminal OUTPUT
$ 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.

03

Change and remove

Take one value out, or empty the set completely.

Changingdeleteclear
delete(value)

Removes that value.

See the example
Gives true if it was thereChanges it
clear()

Removes every value at once.

See the example
Gives undefinedChanges it
change.jsJAVASCRIPT
const 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.

Terminal OUTPUT
$ node change.js
true
false
Set(2) { 'asha', 'meera' }
0
04

Loop through

Walk through every value in the order you added them, or turn the set into an array.

Loopingfor...offorEachspread
for (const v of set)

Visits every value, oldest first.

See the example
Gives nothingOnly reads
forEach(value, value)

Runs your helper on every value. It passes the value twice.

See the example
Gives nothingOnly reads
[...set]

Spreads into an array so you can map, filter or sort.

See the example
Gives an arrayMakes a copy
loop.jsJAVASCRIPT
const 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.

Terminal OUTPUT
$ node loop.js
red
amber
violet
true
true
true
[ 'RED', 'AMBER', 'VIOLET' ]
05

Set maths

Combine two sets: everything in either, only what they share, or what one has that the other does not.

New in 2024unionintersectiondifference
MethodGives youFrontend + backend teams
a.union(b)everything in either setall four people
a.intersection(b)only what is in bothpeople on both teams
a.difference(b)in a but not in bfrontend only
a.symmetricDifference(b)in exactly one of themnot on both
a.isSubsetOf(b)true if all of a is in byes or no
a.isSupersetOf(b)true if all of b is in ayes or no
a.isDisjointFrom(b)true if they share nothingyes or no
maths.jsJAVASCRIPT
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.

Terminal OUTPUT
$ node maths.js
Set(4) { 'asha', 'ravi', 'meera', 'kiran' }
Set(2) { 'asha', 'meera' }
Set(1) { 'ravi' }
Set(2) { 'ravi', 'kiran' }
true true
true
older.jsJAVASCRIPT
// 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.

Terminal OUTPUT
$ node older.js
Set(5) { 1, 2, 3, 4, 5 } Set(2) { 3, 4 } Set(2) { 1, 2 }
06

Set or array?

Know when a Set is the better choice than an array, and when it is not.

Choosingspeedorder
QuestionArraySet
Can values repeat?YesNo
Can I read by position?Yes list[2]No
Is "is it in here?" fast on big lists?No checks one by oneYes
Keeps the order you added?YesYes
Has map, filter, sort?YesNo spread first
Works with JSON?YesNo convert with [...set]
Best forordered lists and positionsunique values and fast lookups
07

WeakSet

Mark objects as seen or done without keeping them in memory forever.

MemoryWeakSetobjects only

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.

weak.jsJAVASCRIPT
// 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.

Terminal OUTPUT
$ node weak.js
processing order 42
order 42 already done
TypeError
undefined
08

All together

Combine role permissions for a user and check what an action needs.

Putting it togetherunionisSubsetOfdifference

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.

access.jsJAVASCRIPT
// 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.

Terminal OUTPUT
$ node access.js
[ 'read', 'write', 'invoice' ]
true
false
missing: delete
TerminalBASH
# 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 toUseYou get
Make a setnew Set()an empty set
Remove repeats from an array[...new Set(list)]a unique array
Add a valueset.add(v)the same set
Check a valueset.has(v)true or false
Count valuesset.sizea number
Remove a valueset.delete(v)true or false
Loop over itfor (const v of set)every value, in order
Everything in eithera.union(b)a new set
Only what both sharea.intersection(b)a new set
In a but not ba.difference(b)a new set
All of a inside b?a.isSubsetOf(b)true or false
Mark objects without leaksnew WeakSet()a weak set