Imagine you run a school event. Students scan their ID to enter. Some students try to scan twice. You only want to count each student once.
With a regular array this is painful:
// ❌ With an array — you must manually check for duplicates
const attended = [];
function recordAttendance(id) {
if (!attended.includes(id)) { // ← extra check every time
attended.push(id);
}
}
recordAttendance("S001");
recordAttendance("S002");
recordAttendance("S001"); // duplicate — must be blocked manually
console.log(attended); // ["S001", "S002"]With a Set, duplicates are blocked automatically:
// ✅ With a Set — duplicates ignored automatically
const attended = new Set();
attended.add("S001");
attended.add("S002");
attended.add("S001"); // duplicate — silently ignored!
console.log(attended.size); // 2
console.log(attended); // Set(2) { "S001", "S002" }A Set is a collection that stores unique values only. Every value in a Set appears exactly once · adding the same value a second time simply does nothing.
Key Differences: Set vs Array
| Feature | Array | Set |
|---|---|---|
| Allows duplicates | ✅ Yes | ❌ No (unique only) |
| Keeps insertion order | ✅ Yes | ✅ Yes |
| Index-based access | ✅ arr[2] | ❌ No index access |
Fast has() check | ❌ O(n) scan | ✅ O(1) hash lookup |
| Iteration | ✅ for, forEach, map... | ✅ for...of, forEach |
size / length | .length | .size |
| Built-in maths (union, etc.) | ❌ Manual only | ✅ Native methods |
💡 TIP: Think of a Set like a guest list at a club · each name appears only once. No matter how many times the same person knocks, they are only counted once.
🏢 REAL WORLD: Sets are used for: tracking which users have viewed a post, storing unique tags on a blog article, removing duplicates from search results, permission systems (a user either has a permission or doesn't), and implementing mathematical set operations on data.
Phase 1 · Conceptual Understanding
Creating a Set
Use the new Set() constructor. You can pass any iterable (array, string, another Set) to initialise it.
// Empty Set
const s1 = new Set();
console.log(s1); // Set(0) {}
// From an array — duplicates are removed automatically
const s2 = new Set([1, 2, 3, 2, 1, 4]);
console.log(s2); // Set(4) { 1, 2, 3, 4 }
// From a string — each CHARACTER becomes an element
const s3 = new Set("HELLO");
console.log(s3); // Set(4) { "H", "E", "L", "O" }
// "L" appeared twice but is stored only once!
// From another Set
const s4 = new Set(s2);
console.log(s4); // Set(4) { 1, 2, 3, 4 }▶ Expected Output:
Set(0) {}
Set(4) { 1, 2, 3, 4 }
Set(4) { "H", "E", "L", "O" }
Set(4) { 1, 2, 3, 4 }🤔 THINK ABOUT IT:
new Set("HELLO")gives{"H", "E", "L", "O"}· 4 elements, not 5. Why? Because "L" appears at position 2 and 3, but a Set only keeps one copy. Can you predict whatnew Set("MISSISSIPPI").sizeequals? Answer:new Set("MISSISSIPPI")→{ "M", "I", "S", "P" }→ size 4.
The size Property
size is the Set equivalent of length in arrays. It always reflects the count of unique values.
const fruits = new Set(["Apple", "Banana", "Mango", "Apple", "Banana"]);
console.log(fruits.size); // 3 ← only 3 unique values⚠️ WATCH OUT: Sets use
.size, not.length. Calling.lengthon a Set returnsundefined.const s = new Set([1, 2, 3]); console.log(s.size); // 3 ✅ console.log(s.length); // undefined ❌
What Types Can a Set Store?
A Set can store any JavaScript value · primitives AND objects:
const mixed = new Set();
mixed.add(42);
mixed.add("hello");
mixed.add(true);
mixed.add(null);
mixed.add(undefined);
mixed.add({ name: "Amara" }); // objects by reference
mixed.add([1, 2, 3]); // arrays by reference
console.log(mixed.size); // 7Sets and Object Identity
For objects and arrays, Sets use reference equality · two objects with the same content are treated as different if they are not the same reference in memory.
const s = new Set();
const obj1 = { name: "Alice" };
const obj2 = { name: "Alice" }; // same content, DIFFERENT reference!
const obj3 = obj1; // SAME reference as obj1
s.add(obj1);
s.add(obj2); // Added! (different reference)
s.add(obj3); // NOT added (same reference as obj1)
console.log(s.size); // 2 — obj1 and obj2 (obj3 is same as obj1)▶ Expected Output: 2
🐛 COMMON MISTAKE:
new Set([{a:1}, {a:1}]).sizereturns2, not1. Objects are compared by reference (are they the same object in memory?), not by content. If you want unique objects by content, you need to compare serialised strings likeJSON.stringify(obj).
Iterating a Set
Sets are iterable · you can loop through them in several ways. They always iterate in insertion order.
for...of:
const fruits = new Set(["Apple", "Banana", "Mango"]);
for (const fruit of fruits) {
console.log(fruit);
}▶ Expected Output:
Apple
Banana
MangoforEach(value, value, set):
fruits.forEach((value) => {
console.log(value);
});💡 TIP: Notice the
forEachcallback receives(value, value, set)· the first and second arguments are BOTH the value (not index + value like arrays). This is intentional: Sets have no index concept, so the key IS the value to stay consistent withMap'sforEach(value, key, map)signature.
Spread into an Array (very common pattern):
const fruitsArray = [...fruits];
console.log(fruitsArray); // ["Apple", "Banana", "Mango"]Using Array.from():
const fruitsArray2 = Array.from(fruits);
console.log(fruitsArray2); // ["Apple", "Banana", "Mango"]🏢 REAL WORLD: Converting a Set to an array (
[...mySet]) is the standard way to use Set as a step in a pipeline · e.g.[...new Set(arr)]is the most common one-liner to deduplicate an array in modern JavaScript.
The Fastest Way to Deduplicate an Array
const raw = [1, 2, 3, 2, 4, 1, 5, 3];
// One-liner deduplication using Set + spread
const unique = [...new Set(raw)];
console.log(unique); // [1, 2, 3, 4, 5]
// Deduplicate an array of strings
const tags = ["js", "web", "js", "html", "css", "web", "js"];
const uniqueTags = [...new Set(tags)];
console.log(uniqueTags); // ["js", "web", "html", "css"]▶ Expected Output:
[1, 2, 3, 4, 5]
["js", "web", "html", "css"]🏢 REAL WORLD:
[...new Set(arr)]is one of the most commonly used JavaScript one-liners in professional code. It appears in React component rendering, API data deduplication, and tag/keyword processing everywhere.
Phase 1 · Conceptual Understanding
Sets have a focused, clean API. Every method has one clear job.
add(value) · Add a Value
Adds a value to the Set. If the value already exists, nothing happens. Returns the Set itself (allowing chaining).
const s = new Set();
s.add(1);
s.add(2);
s.add(3);
s.add(2); // duplicate — silently ignored
s.add(1); // duplicate — silently ignored
console.log(s); // Set(3) { 1, 2, 3 }
console.log(s.size); // 3▶ Expected Output:
Set(3) { 1, 2, 3 }
3Chaining add() calls:
Because add() returns the Set itself, you can chain:
const s = new Set();
s.add(10).add(20).add(30).add(20); // 20 is a duplicate
console.log(s); // Set(3) { 10, 20, 30 }has(value) · Check if a Value Exists
Returns true if the value is in the Set, false otherwise. This is very fast (O(1) hash lookup · doesn't have to scan every element like array includes()).
const fruits = new Set(["Apple", "Banana", "Mango"]);
console.log(fruits.has("Banana")); // true
console.log(fruits.has("Kiwi")); // false▶ Expected Output:
true
false💡 TIP: For large collections,
Set.has()is significantly faster thanArray.includes(). If you're checking membership frequently on a large list, convert it to a Set first.
Performance comparison:
const bigArray = Array.from({ length: 100000 }, (_, i) => i);
const bigSet = new Set(bigArray);
// Array.includes() — scans up to 100,000 elements (slow for last item)
console.time("array");
bigArray.includes(99999);
console.timeEnd("array"); // ~1–5ms
// Set.has() — instant hash lookup regardless of size
console.time("set");
bigSet.has(99999);
console.timeEnd("set"); // ~0.01msdelete(value) · Remove a Value
Removes a value from the Set. Returns true if the value existed and was removed, false if it wasn't found.
const fruits = new Set(["Apple", "Banana", "Mango"]);
const wasRemoved = fruits.delete("Banana");
console.log(wasRemoved); // true
console.log(fruits); // Set(2) { "Apple", "Mango" }
const notFound = fruits.delete("Kiwi");
console.log(notFound); // false (wasn't in the Set)
console.log(fruits.size); // 2 (unchanged)▶ Expected Output:
true
Set(2) { "Apple", "Mango" }
false
2clear() · Remove All Values
Removes every element from the Set. The Set still exists but is now empty.
const fruits = new Set(["Apple", "Banana", "Mango"]);
console.log(fruits.size); // 3
fruits.clear();
console.log(fruits.size); // 0
console.log(fruits); // Set(0) {}▶ Expected Output:
3
0
Set(0) {}values() · Iterator of All Values
Returns an iterator object that yields each value in insertion order. You can use this directly in for...of.
const fruits = new Set(["Apple", "Banana", "Mango"]);
const iterator = fruits.values();
console.log(iterator.next().value); // "Apple"
console.log(iterator.next().value); // "Banana"
console.log(iterator.next().value); // "Mango"
console.log(iterator.next().done); // true (iterator exhausted)More commonly used in for...of:
for (const val of fruits.values()) {
console.log(val);
}
// Apple
// Banana
// Mangokeys() · Same as values() for Sets
For consistency with Map, Sets have a keys() method · but it behaves identically to values() since Sets have no separate keys.
const fruits = new Set(["Apple", "Banana"]);
for (const key of fruits.keys()) {
console.log(key); // "Apple", then "Banana"
}entries() · Iterator of [value, value] Pairs
Returns an iterator of [value, value] pairs · again for Map consistency. Both entries in each pair are the same value.
const fruits = new Set(["Apple", "Banana", "Mango"]);
for (const [key, val] of fruits.entries()) {
console.log(key, "→", val);
}▶ Expected Output:
Apple → Apple
Banana → Banana
Mango → Mango💡 TIP: You'll rarely use
entries()on a Set directly. It exists so that code written for Maps can work with Sets too without modification.
Summary: All Set Methods
| Method | What it does | Returns |
|---|---|---|
new Set(iterable?) | Create a Set | Set |
.add(value) | Add a value (no-op if exists) | The Set (chainable) |
.has(value) | Check existence | Boolean |
.delete(value) | Remove a value | Boolean (was it there?) |
.clear() | Remove all values | undefined |
.values() | Iterator of values | SetIterator |
.keys() | Same as values() | SetIterator |
.entries() | Iterator of [val, val] pairs | SetIterator |
.forEach(fn) | Run function for each value | undefined |
.size | Count of unique elements | Number |
Phase 1 · Conceptual Understanding
One of the most powerful features of the modern JavaScript Set (introduced in ES2025 as native methods, and achievable with custom logic before that) is the ability to perform mathematical set operations:
| Operation | Question It Answers | Example |
|---|---|---|
| Union | All elements from both sets | A ∪ B |
| Intersection | Elements in BOTH sets | A ∩ B |
| Difference | Elements in A but NOT in B | A − B |
| Symmetric Difference | Elements in ONE set but not both | A △ B |
| isSubsetOf | Is every element of A in B? | A ⊆ B |
| isSupersetOf | Does A contain all of B? | A ⊇ B |
| isDisjointFrom | Do A and B share no elements? | A ∩ B = ∅ |
🏢 REAL WORLD: These operations appear constantly in real apps: "which users have BOTH permissions A and B?" (intersection), "show all products from either category" (union), "which students enrolled last year but NOT this year?" (difference).
Visual Understanding First
Before any code, let's visualise with two sets:
Set A = { 1, 2, 3, 4, 5 }
Set B = { 3, 4, 5, 6, 7 }
┌──────────────────────────┐
┌──────────┤ ├──────────┐
│ 1 2 │ 3 4 5 │ 6 7 │
│ (A only)│ (both A & B) │ (B only) │
└──────────┤ ├──────────┘
└──────────────────────────┘
Union A ∪ B = { 1, 2, 3, 4, 5, 6, 7 } (everything)
Intersection A ∩ B = { 3, 4, 5 } (overlap only)
Difference A − B = { 1, 2 } (A only)
Difference B − A = { 6, 7 } (B only)
Symmetric Diff = { 1, 2, 6, 7 } (non-overlap)Native Set Methods (ES2025+)
Modern JavaScript now has these as native Set methods. Always use these when available:
const A = new Set([1, 2, 3, 4, 5]);
const B = new Set([3, 4, 5, 6, 7]);union(other) · All Elements from Both Sets (A ∪ B)
Combines every element from both Sets into a new Set (no duplicates).
const A = new Set([1, 2, 3, 4, 5]);
const B = new Set([3, 4, 5, 6, 7]);
const unionAB = A.union(B);
console.log(unionAB); // Set(7) { 1, 2, 3, 4, 5, 6, 7 }▶ Expected Output: Set(7) { 1, 2, 3, 4, 5, 6, 7 }
🏢 REAL WORLD: A school wants to email all students enrolled in either maths or science.
mathStudents.union(scienceStudents)gives the complete list with no duplicates.
intersection(other) · Elements in BOTH Sets (A ∩ B)
Returns a new Set of only the elements that appear in both sets.
const A = new Set([1, 2, 3, 4, 5]);
const B = new Set([3, 4, 5, 6, 7]);
const intersectAB = A.intersection(B);
console.log(intersectAB); // Set(3) { 3, 4, 5 }▶ Expected Output: Set(3) { 3, 4, 5 }
🏢 REAL WORLD: Find users who have both the "admin" permission and the "editor" permission:
adminUsers.intersection(editorUsers).
difference(other) · Elements in A but NOT in B (A − B)
Returns a new Set of elements that exist in the first set but are absent from the second.
const A = new Set([1, 2, 3, 4, 5]);
const B = new Set([3, 4, 5, 6, 7]);
const diffAB = A.difference(B); // In A but not B
const diffBA = B.difference(A); // In B but not A
console.log(diffAB); // Set(2) { 1, 2 }
console.log(diffBA); // Set(2) { 6, 7 }▶ Expected Output:
Set(2) { 1, 2 }
Set(2) { 6, 7 }⚠️ WATCH OUT:
A.difference(B)≠B.difference(A). The order matters! The first set is the "source", the second is the "exclude" list.
🏢 REAL WORLD: "Which students attended last year but have NOT enrolled this year?" →
lastYearStudents.difference(thisYearStudents).
symmetricDifference(other) · Elements in ONE but NOT BOTH (A △ B)
Returns a new Set of elements that are in either set but not in both. It is the opposite of intersection · everything that is NOT in the overlap.
const A = new Set([1, 2, 3, 4, 5]);
const B = new Set([3, 4, 5, 6, 7]);
const symDiff = A.symmetricDifference(B);
console.log(symDiff); // Set(4) { 1, 2, 6, 7 }▶ Expected Output: Set(4) { 1, 2, 6, 7 }
💡 TIP: Symmetric Difference is the same as
(A ∪ B) - (A ∩ B)· the union minus the overlap. It tells you "what is exclusive to each set, excluding the shared parts".
🏢 REAL WORLD: "Which products changed between two inventory snapshots?" · items that were added OR removed (not the unchanged ones in both).
isSubsetOf(other) · Is Every Element of A Found in B?
Returns true if all elements of the Set are also in the other Set.
const A = new Set([3, 4]);
const B = new Set([1, 2, 3, 4, 5]);
console.log(A.isSubsetOf(B)); // true — 3 and 4 are both in B
console.log(B.isSubsetOf(A)); // false — B has elements not in A▶ Expected Output:
true
false🏢 REAL WORLD: "Does this user have all the required permissions?" →
requiredPerms.isSubsetOf(userPerms).
isSupersetOf(other) · Does A Contain ALL Elements of B?
The reverse of isSubsetOf. Returns true if A contains every element of B.
const A = new Set([1, 2, 3, 4, 5]);
const B = new Set([3, 4]);
console.log(A.isSupersetOf(B)); // true — A contains all of B
console.log(B.isSupersetOf(A)); // false — B doesn't contain all of A▶ Expected Output:
true
false💡 TIP:
A.isSubsetOf(B)is always the mirror ofB.isSupersetOf(A). They answer the same question from opposite sides.
isDisjointFrom(other) · Do the Sets Share NO Common Elements?
Returns true if the two Sets have zero elements in common (empty intersection).
const A = new Set([1, 2, 3]);
const B = new Set([4, 5, 6]);
const C = new Set([3, 4, 5]);
console.log(A.isDisjointFrom(B)); // true — no shared elements
console.log(A.isDisjointFrom(C)); // false — 3 is in both▶ Expected Output:
true
false🏢 REAL WORLD: "Are these two product categories completely separate?" →
categoryA.isDisjointFrom(categoryB). Iftrue, they share no products.
Manual Implementations (for older environments without native methods)
If the native Set methods aren't available yet in your environment, here are the manual equivalents using spread and filter:
const A = new Set([1, 2, 3, 4, 5]);
const B = new Set([3, 4, 5, 6, 7]);
// Union: spread both into a new Set
const union = new Set([...A, ...B]);
console.log([...union]); // [1, 2, 3, 4, 5, 6, 7]
// Intersection: keep elements of A that B also has
const intersection = new Set([...A].filter(x => B.has(x)));
console.log([...intersection]); // [3, 4, 5]
// Difference A - B: keep elements of A that B does NOT have
const difference = new Set([...A].filter(x => !B.has(x)));
console.log([...difference]); // [1, 2]
// Symmetric difference: elements in one but not both
const symDiff = new Set([
...[...A].filter(x => !B.has(x)),
...[...B].filter(x => !A.has(x))
]);
console.log([...symDiff]); // [1, 2, 6, 7]
// isSubset: every element of A is in B
const isSubset = [...A].every(x => B.has(x));
console.log(isSubset); // false
// isDisjoint: no element of A is in B
const isDisjoint = [...A].every(x => !B.has(x));
console.log(isDisjoint); // falseAll Set Logic Methods at a Glance
const A = new Set(["Alice", "Bob", "Carol"]);
const B = new Set(["Bob", "Carol", "David"]);
console.log([...A.union(B)]); // ["Alice","Bob","Carol","David"]
console.log([...A.intersection(B)]); // ["Bob","Carol"]
console.log([...A.difference(B)]); // ["Alice"]
console.log([...B.difference(A)]); // ["David"]
console.log([...A.symmetricDifference(B)]); // ["Alice","David"]
console.log(A.isSubsetOf(B)); // false
console.log(A.isSupersetOf(new Set(["Bob","Carol"]))); // true
console.log(A.isDisjointFrom(new Set(["X","Y"]))); // truePhase 1 · Conceptual Understanding
A WeakSet is a special variant of Set with two important differences:
- It can only store objects (not primitives like numbers or strings)
- It holds its objects weakly · meaning the garbage collector can remove them from the WeakSet automatically if nothing else in the program holds a reference to that object
What Does "Weakly Held" Mean?
Think of a regular Set like a strong grip · it holds onto objects and prevents them from being cleaned up. Think of a WeakSet like a sticky note · if the object it points to no longer exists anywhere else, the WeakSet's reference is automatically discarded. This prevents memory leaks.
Regular Set:
Object ←──── strong reference ──── Set
(Object CANNOT be garbage collected while Set exists)
WeakSet:
Object ←──── weak reference ──── WeakSet
(Object CAN be garbage collected even if WeakSet exists)Creating a WeakSet
const ws = new WeakSet();
const obj1 = { name: "Alice" };
const obj2 = { name: "Bob" };
ws.add(obj1);
ws.add(obj2);
ws.add(obj1); // duplicate — ignored
console.log(ws.has(obj1)); // true
console.log(ws.has(obj2)); // true▶ Expected Output:
true
trueWeakSet Can ONLY Store Objects
const ws = new WeakSet();
// ❌ Primitives are NOT allowed
try {
ws.add(42); // TypeError!
} catch (e) {
console.log(e.message); // "Invalid value used in weak set"
}
try {
ws.add("hello"); // TypeError!
} catch (e) {
console.log(e.message);
}
// ✅ Only objects and arrays (which are objects)
ws.add({ id: 1 });
ws.add([1, 2, 3]);
ws.add(function() {}); // functions are objects tooWeakSet Methods · Only Three!
WeakSet intentionally has a very limited API:
| Method | What it does | Returns |
|---|---|---|
.add(object) | Add an object | The WeakSet |
.has(object) | Check if object is in the set | Boolean |
.delete(object) | Remove an object | Boolean |
⚠️ WATCH OUT: WeakSet has NO
size,clear(),forEach(), or iteration methods. You cannot loop over a WeakSet or check how many items it contains. This is intentional · because the garbage collector may remove items at any time, the count would be unreliable.
const ws = new WeakSet();
const obj = { name: "Test" };
ws.add(obj);
console.log(ws.has(obj)); // true
ws.delete(obj);
console.log(ws.has(obj)); // false
// ❌ These do NOT exist on WeakSet:
// ws.size → undefined
// ws.clear() → TypeError
// for...of ws → TypeError (not iterable)Automatic Memory Cleanup · The Key Benefit
let obj = { name: "Temporary" };
const ws = new WeakSet([obj]);
console.log(ws.has(obj)); // true
// When we clear the last reference to the object...
obj = null; // ← nothing else references the object now
// ...the garbage collector will eventually remove it from ws automatically
// ws.has(obj) → false (once GC runs)💡 TIP: You cannot directly observe garbage collection in JavaScript · it happens automatically. The point is that WeakSet does NOT prevent it from happening, unlike regular Set.
When to Use WeakSet · Practical Example
The most common real-world use of WeakSet is tracking objects without preventing their cleanup.
Example: Mark DOM elements that have been processed
const processedElements = new WeakSet();
function processElement(el) {
if (processedElements.has(el)) {
console.log("Already processed, skipping");
return;
}
// ... do processing ...
processedElements.add(el);
console.log("Processed:", el.id || "element");
}
// When a DOM element is removed from the page,
// processedElements automatically releases it — no memory leak!Example: Track which objects have been validated
const validated = new WeakSet();
function validate(user) {
// Run expensive validation once, cache the result
if (validated.has(user)) return true;
const isValid = user.name && user.email; // simplified
if (isValid) validated.add(user);
return isValid;
}
const alice = { name: "Alice", email: "alice@example.com" };
console.log(validate(alice)); // true — runs full validation
console.log(validate(alice)); // true — returns cached result🏢 REAL WORLD: WeakSet is used in JavaScript frameworks and libraries to track which components or DOM nodes have been "seen" or "processed" · without creating memory leaks when those components are destroyed.
Set vs WeakSet · Full Comparison
| Feature | Set | WeakSet |
|---|---|---|
| Value types | Any (primitives + objects) | Objects ONLY |
| Holds references | Strongly (prevents GC) | Weakly (allows GC) |
size property | ✅ Yes | ❌ No |
Iterable (for...of) | ✅ Yes | ❌ No |
forEach() | ✅ Yes | ❌ No |
clear() | ✅ Yes | ❌ No |
add() | ✅ Yes | ✅ Yes |
has() | ✅ Yes | ✅ Yes |
delete() | ✅ Yes | ✅ Yes |
| Use case | General unique value storage | Track objects without memory leaks |
Quick reference for every Set and WeakSet feature.
Set · Constructor
new Set() // empty set
new Set(iterable) // from array, string, another set, etc.Set · Properties
| Property | Type | Description |
|---|---|---|
set.size | Number | Count of unique elements |
set[Symbol.iterator] | Function | Makes Set iterable in for...of |
Set · Instance Methods
| Method | Returns | Description |
|---|---|---|
set.add(value) | Set | Add value; no-op if exists |
set.has(value) | Boolean | true if value is in set |
set.delete(value) | Boolean | Remove value; true if found |
set.clear() | undefined | Remove all values |
set.values() | SetIterator | Iterate values |
set.keys() | SetIterator | Same as values() |
set.entries() | SetIterator | Iterate [value, value] pairs |
set.forEach(fn) | undefined | Run fn(value, value, set) for each |
Set · Logic Methods (ES2025 Native)
| Method | Returns | Description |
|---|---|---|
set.union(other) | New Set | A ∪ B · all elements from both |
set.intersection(other) | New Set | A ∩ B · only elements in both |
set.difference(other) | New Set | A − B · in A but not B |
set.symmetricDifference(other) | New Set | A △ B · in one but not both |
set.isSubsetOf(other) | Boolean | Is every element of A in B? |
set.isSupersetOf(other) | Boolean | Does A contain all of B? |
set.isDisjointFrom(other) | Boolean | Do A and B share no elements? |
Set · Common Conversion Patterns
const s = new Set([1, 2, 3]);
// Set → Array
const arr1 = [...s]; // [1, 2, 3]
const arr2 = Array.from(s); // [1, 2, 3]
// Array → Set (deduplicate)
const unique = new Set([1, 2, 2, 3, 3]); // Set { 1, 2, 3 }
// String → Set of unique characters
const chars = new Set("hello"); // Set { "h", "e", "l", "o" }WeakSet · Constructor
new WeakSet() // empty
new WeakSet(iterable) // from iterable of objectsWeakSet · Instance Methods (Only Three)
| Method | Returns | Description |
|---|---|---|
ws.add(object) | WeakSet | Add an object (must be object) |
ws.has(object) | Boolean | Check if object is in WeakSet |
ws.delete(object) | Boolean | Remove object |
Set Operation Quick Reference · Visual Summary
A = { 1, 2, 3, 4 } B = { 3, 4, 5, 6 }
A.union(B) → { 1, 2, 3, 4, 5, 6 }
A.intersection(B) → { 3, 4 }
A.difference(B) → { 1, 2 }
B.difference(A) → { 5, 6 }
A.symmetricDifference(B) → { 1, 2, 5, 6 }
A.isSubsetOf(B) → false
new Set([3,4]).isSubsetOf(A) → true
A.isSupersetOf(new Set([1,2])) → true
A.isDisjointFrom(new Set([7])) → true
A.isDisjointFrom(B) → false (3 and 4 are shared)Phase 2 · Applied Exercises
Exercise 1 · Unique Value Enforcer 🔒
Objective: Practice creating Sets, using add, has, delete, and converting between Sets and arrays.
Scenario: You're building a tag management system for a blog platform. Tags must be unique, case-insensitive, and stored efficiently.
Warm-up Micro-Demo:
const tags = new Set(["javascript", "web", "javascript", "css"]);
console.log("Tags:", [...tags]); // ["javascript", "web", "css"]
console.log("Count:", tags.size); // 3▶ Expected Output:
Tags: ["javascript", "web", "css"]
Count: 3Task A · Build the Tag System
// Normalise tag: lowercase and trimmed
function normaliseTag(tag) {
return tag.trim().toLowerCase();
}
class TagManager {
constructor() {
this.tags = new Set();
}
addTag(tag) {
const normalised = normaliseTag(tag);
if (this.tags.has(normalised)) {
console.log(`"${normalised}" already exists`);
return false;
}
this.tags.add(normalised);
console.log(`Added: "${normalised}"`);
return true;
}
removeTag(tag) {
const normalised = normaliseTag(tag);
const removed = this.tags.delete(normalised);
console.log(removed ? `Removed: "${normalised}"` : `"${normalised}" not found`);
return removed;
}
hasTag(tag) {
return this.tags.has(normaliseTag(tag));
}
getAll() {
return [...this.tags].sort();
}
}
const manager = new TagManager();
manager.addTag("JavaScript");
manager.addTag(" CSS "); // spaces trimmed
manager.addTag("javascript"); // duplicate (normalised)
manager.addTag("React");
manager.addTag("css"); // duplicate after normalise
console.log("\nAll tags:", manager.getAll());
console.log("Has 'react':", manager.hasTag("React"));
manager.removeTag("CSS");
console.log("After removal:", manager.getAll());Expected Output:
Added: "javascript"
Added: "css"
"javascript" already exists
Added: "react"
"css" already exists
All tags: ["css", "javascript", "react"]
Has 'react': true
Removed: "css"
After removal: ["javascript", "react"]Self-check questions:
- Why is
Set.has()preferred overArray.includes()for this use case? - What would happen if we used an array instead of a Set for
this.tags? - Why do we normalise tags before adding them?
Exercise 2 · Set Logic Challenge 🔢
Objective: Practice all six set logic operations on real-world data.
Scenario: You work at a university. Three subject sets exist: students enrolled in Maths, Computer Science, and Physics.
Warm-up Micro-Demo:
const mathsClass = new Set(["Alice", "Bob", "Carol"]);
const scienceClass = new Set(["Bob", "Carol", "David"]);
const both = mathsClass.intersection(scienceClass);
console.log("In both:", [...both]); // ["Bob", "Carol"]Task A · Enrolment Analysis
const maths = new Set(["Alice", "Bob", "Carol", "David", "Eve" ]);
const cs = new Set(["Bob", "Carol", "Frank", "Grace", "Henry"]);
const phys = new Set(["Carol", "David", "Grace", "Ivan", "Alice"]);
// 1. All students across all subjects
const allStudents = maths.union(cs).union(phys);
console.log("All students :", allStudents.size, "→", [...allStudents].sort());
// 2. Students in BOTH Maths AND CS
const mathsAndCS = maths.intersection(cs);
console.log("Maths & CS :", [...mathsAndCS]);
// 3. Students in ALL THREE subjects
const allThree = maths.intersection(cs).intersection(phys);
console.log("All three :", [...allThree]);
// 4. Students ONLY in Maths (not CS, not Physics)
const mathsOnly = maths.difference(cs).difference(phys);
console.log("Maths only :", [...mathsOnly]);
// 5. Students in Maths OR CS but NOT both
const mathsOrCSExclusive = maths.symmetricDifference(cs);
console.log("Maths XOR CS :", [...mathsOrCSExclusive].sort());
// 6. Is {Bob, Carol} a subset of maths?
const checkSubset = new Set(["Bob", "Carol"]);
console.log("Bob+Carol ⊆ Maths:", checkSubset.isSubsetOf(maths)); // true
// 7. Are Maths and Physics disjoint?
console.log("Maths ∩ Phys = ∅:", maths.isDisjointFrom(phys)); // false (Alice, David, Carol overlap)Expected Output:
All students : 10 → ["Alice","Bob","Carol","David","Eve","Frank","Grace","Henry","Ivan"]
Maths & CS : ["Bob", "Carol"]
All three : ["Carol"]
Maths only : ["Eve"]
Maths XOR CS : ["Alice","David","Eve","Frank","Grace","Henry"]
Bob+Carol ⊆ Maths: true
Maths ∩ Phys = ∅: falseSelf-check questions:
- What is the difference between
difference()andsymmetricDifference()? - Why is
A.isSubsetOf(B)the mirror ofB.isSupersetOf(A)? - What does it mean for two sets to be "disjoint"?
Exercise 3 · WeakSet Validator ♻️
Objective: Practice creating and using a WeakSet to track object state without memory leaks.
Scenario: You're building a form validation system. Each form section is an object. You want to track which sections have been validated without keeping references that prevent cleanup.
Warm-up Micro-Demo:
const validatedSections = new WeakSet();
const section = { id: "personal", filled: true };
validatedSections.add(section);
console.log(validatedSections.has(section)); // trueTask A · Section Validation Tracker
const validatedSections = new WeakSet();
const validationErrors = new WeakSet();
function validateSection(section) {
if (validatedSections.has(section)) {
return { cached: true, valid: !validationErrors.has(section) };
}
// Simulate validation: all required fields must be non-empty
const isValid = section.fields.every(f => f.value.trim() !== "");
validatedSections.add(section);
if (!isValid) validationErrors.add(section);
return { cached: false, valid: isValid };
}
const nameSection = {
id: "name",
fields: [{ name: "firstName", value: "Alice" }, { name: "lastName", value: "Smith" }]
};
const emailSection = {
id: "email",
fields: [{ name: "email", value: "" }] // empty!
};
const addressSection = {
id: "address",
fields: [{ name: "street", value: "123 Main St" }, { name: "city", value: "Lagos" }]
};
let result;
result = validateSection(nameSection);
console.log(`Name : valid=${result.valid}, cached=${result.cached}`);
result = validateSection(emailSection);
console.log(`Email : valid=${result.valid}, cached=${result.cached}`);
result = validateSection(nameSection); // second call — should be cached
console.log(`Name(2) : valid=${result.valid}, cached=${result.cached}`);
result = validateSection(addressSection);
console.log(`Address : valid=${result.valid}, cached=${result.cached}`);
// Check if all three sections passed
const sections = [nameSection, emailSection, addressSection];
const allValid = sections.every(s => !validationErrors.has(s));
console.log("\nForm ready to submit:", allValid);
// WeakSet characteristics
console.log("\nWeakSet has no size:", typeof validatedSections.size); // undefinedExpected Output:
Name : valid=true, cached=false
Email : valid=false, cached=false
Name(2) : valid=true, cached=true
Address : valid=true, cached=false
Form ready to submit: false
WeakSet has no size: undefinedSelf-check questions:
- Why can't WeakSet store primitive values like
42or"hello"? - What would happen if we used a regular Set instead of WeakSet here?
- Why does WeakSet have no
sizeorforEach?
Phase 3 · Project Simulation
Real-world scenario: You are a developer at a school. Build an Attendance & Permission Tracker that:
- Uses
Setto record daily student attendance (no duplicates) - Computes attendance statistics using Set operations
- Uses Set logic to find students who are absent, early leavers, or have full attendance
- Uses
WeakSetto track which student objects have been flagged for a follow-up call - Generates a full attendance report
🔵 Stage 1 · Record Attendance
Goal: Use Sets to record morning and afternoon attendance rolls and compute who attended, who was absent, and who only attended half the day.
Simple stage preview:
const morningRoll = new Set(["S001", "S002", "S003"]);
const afternoonRoll = new Set(["S002", "S003", "S004"]);
const fullDay = morningRoll.intersection(afternoonRoll);
console.log("Full day:", [...fullDay]); // ["S002", "S003"]Lesson 15 complete! 🎉
You covered:
- ✅ 1. Background: Why Sets Exist
- ✅ 2. Topic 1 · Sets: Basics & Creation
- ✅ 3. Topic 2 · Set Methods
- ✅ 4. Topic 3 · Set Logic
- ✅ 5. Topic 4 · WeakSet
- ✅ 6. Topic 5 · Complete Set Reference
- ✅ 7. Applied Exercises