Set (The No-Duplicates List)
The Duplicate Problem
Imagine you're tracking which effects depend on a piece of state:
const listeners = [];
// Effect 1 runs
listeners.push(effect1);
// Effect 1 runs AGAIN (maybe user scrolled, state changed, etc.)
listeners.push(effect1);
// Effect 1 runs AGAIN
listeners.push(effect1);
console.log(listeners);
// [effect1, effect1, effect1] 😱
// Now when state changes:
listeners.forEach(fn => fn());
// effect1 runs 3 times! 🐛The Problem: You only want each effect to run once, but you've stored it three times!
The Solution: Use a Set! 🎯
const listeners = new Set();
listeners.add(effect1);
listeners.add(effect1); // Ignored - already exists!
listeners.add(effect1); // Ignored - already exists!
console.log(listeners.size); // 1 ✨
listeners.forEach(fn => fn());
// effect1 runs exactly once! ✅What is a Set? (No Jargon)
Simple Definition
A Set is like a list that automatically removes duplicates.
Think of it as a VIP guest list for a party - each person's name can only appear once.
// Regular array (allows duplicates)
const array = ['Alice', 'Bob', 'Alice', 'Alice'];
console.log(array); // ['Alice', 'Bob', 'Alice', 'Alice']
// Set (no duplicates)
const set = new Set(['Alice', 'Bob', 'Alice', 'Alice']);
console.log(set); // Set { 'Alice', 'Bob' }The Key Point
Sets automatically ensure uniqueness. You never have to check "is this already in the list?" - the Set does it for you!
Real-World Analogy
The Email Subscription List
Imagine you run a newsletter:
Array Approach (Allows Duplicates) ❌
Subscriber List (Array):
┌──────────────────────┐
│ alice@email.com │
│ bob@email.com │
│ alice@email.com │ ← Duplicate!
│ charlie@email.com │
│ alice@email.com │ ← Another duplicate!
└──────────────────────┘
Send newsletter:
→ Alice gets 3 emails! 😱
→ Bob gets 1 email
→ Charlie gets 1 emailProblems:
- ❌ People get duplicate emails
- ❌ Wastes resources
- ❌ Annoys subscribers
Set Approach (No Duplicates) ✅
Subscriber List (Set):
┌──────────────────────┐
│ alice@email.com │
│ bob@email.com │
│ charlie@email.com │
└──────────────────────┘
Send newsletter:
→ Alice gets 1 email ✅
→ Bob gets 1 email ✅
→ Charlie gets 1 email ✅Benefits:
- ✅ Each person listed once
- ✅ No duplicates possible
- ✅ Automatic deduplication
- ✅ Cleaner, more efficient
That's exactly what a Set does!
Arrays vs Sets
Visual Comparison
// ARRAY - Allows duplicates
const colors = ['red', 'blue', 'red', 'green', 'blue'];
console.log(colors);
// ['red', 'blue', 'red', 'green', 'blue']
console.log(colors.length); // 5// SET - Removes duplicates automatically
const colors = new Set(['red', 'blue', 'red', 'green', 'blue']);
console.log(colors);
// Set { 'red', 'blue', 'green' }
console.log(colors.size); // 3Side-by-Side Operations
| Operation | Array | Set |
|---|---|---|
| Add item | array.push('x') | set.add('x') |
| Check if exists | array.includes('x') (slow) | set.has('x') (fast) |
| Remove item | array.splice(...) (complex) | set.delete('x') (simple) |
| Get size | array.length | set.size |
| Clear all | array.length = 0 | set.clear() |
| Allows duplicates? | ✅ Yes | ❌ No |
How Set Works
The Basic API
Set has 6 main methods:
const mySet = new Set();
// 1. ADD - Add item (duplicates ignored)
mySet.add('apple');
mySet.add('banana');
mySet.add('apple'); // Ignored!
// 2. HAS - Check if item exists (fast!)
console.log(mySet.has('apple')); // true
console.log(mySet.has('orange')); // false
// 3. DELETE - Remove item
mySet.delete('banana');
console.log(mySet.has('banana')); // false
// 4. SIZE - Get count
console.log(mySet.size); // 1
// 5. CLEAR - Remove everything
mySet.clear();
console.log(mySet.size); // 0
// 6. FOREACH - Loop through items
mySet.add('a');
mySet.add('b');
mySet.forEach(item => console.log(item));
// a
// bCreating Sets
// 1. Empty Set
const set1 = new Set();
// 2. From array
const set2 = new Set([1, 2, 3, 2, 1]);
console.log(set2); // Set { 1, 2, 3 }
// 3. From string (each character)
const set3 = new Set('hello');
console.log(set3); // Set { 'h', 'e', 'l', 'o' }
// 4. Add items one by one
const set4 = new Set();
set4.add(1);
set4.add(2);
set4.add(3);
console.log(set4); // Set { 1, 2, 3 }Step-by-Step: Building Your First Set
Example: Tracking Unique Website Visitors
Let's build a system that counts unique visitors (not total visits).
Step 1: Create the Set
const uniqueVisitors = new Set();Think of this as creating a guest book where each name can only appear once.
Step 2: Add Visitors
// Alice visits
uniqueVisitors.add('Alice');
console.log(uniqueVisitors.size); // 1
// Bob visits
uniqueVisitors.add('Bob');
console.log(uniqueVisitors.size); // 2
// Alice visits AGAIN
uniqueVisitors.add('Alice'); // Ignored - already in set
console.log(uniqueVisitors.size); // Still 2! ✨What's happening:
Visit 1 (Alice):
Set: { 'Alice' }
Size: 1
Visit 2 (Bob):
Set: { 'Alice', 'Bob' }
Size: 2
Visit 3 (Alice again):
Set: { 'Alice', 'Bob' } ← No change!
Size: 2 ← No change!Step 3: Check If Visitor Exists
if (uniqueVisitors.has('Alice')) {
console.log('Welcome back, Alice!');
} else {
console.log('Welcome, new visitor!');
}
// "Welcome back, Alice!"
if (uniqueVisitors.has('Charlie')) {
console.log('Welcome back, Charlie!');
} else {
console.log('Welcome, new visitor!');
}
// "Welcome, new visitor!"Step 4: Get All Unique Visitors
// Convert Set to Array
const visitorList = Array.from(uniqueVisitors);
console.log(visitorList); // ['Alice', 'Bob']
// Or use spread operator
const visitorList2 = [...uniqueVisitors];
console.log(visitorList2); // ['Alice', 'Bob']
// Or loop directly
uniqueVisitors.forEach(name => {
console.log(`Visitor: ${name}`);
});
// Visitor: Alice
// Visitor: BobStep 5: Remove a Visitor
// Bob leaves permanently
uniqueVisitors.delete('Bob');
console.log(uniqueVisitors.has('Bob')); // false
console.log(uniqueVisitors.size); // 1
console.log([...uniqueVisitors]); // ['Alice']Common Set Operations
Operation 1: Remove Duplicates from Array
The Problem:
const numbers = [1, 2, 3, 2, 4, 1, 5, 3];
// Want: [1, 2, 3, 4, 5]The Solution:
const numbers = [1, 2, 3, 2, 4, 1, 5, 3];
// Method 1: Using Set and spread
const unique = [...new Set(numbers)];
console.log(unique); // [1, 2, 3, 4, 5]
// Method 2: Using Array.from
const unique2 = Array.from(new Set(numbers));
console.log(unique2); // [1, 2, 3, 4, 5]One-liner magic:
// Duplicate removal in one line! ✨
const unique = [...new Set([1, 2, 3, 2, 4, 1, 5, 3])];Operation 2: Union (Combine Two Sets)
Combine two sets into one:
const setA = new Set([1, 2, 3]);
const setB = new Set([3, 4, 5]);
// Union: All items from both sets
const union = new Set([...setA, ...setB]);
console.log(union); // Set { 1, 2, 3, 4, 5 }Visual:
setA: { 1, 2, 3 }
setB: { 3, 4, 5 }
↓
Union: { 1, 2, 3, 4, 5 }Operation 3: Intersection (Common Items)
Find items in both sets:
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);
// Intersection: Items in both sets
const intersection = new Set(
[...setA].filter(x => setB.has(x))
);
console.log(intersection); // Set { 3, 4 }Visual:
setA: { 1, 2, 3, 4 }
setB: { 3, 4, 5, 6 }
↓
Intersection: { 3, 4 } ← Only these appear in both!Operation 4: Difference (Items in A but not B)
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);
// Difference: Items in A that are NOT in B
const difference = new Set(
[...setA].filter(x => !setB.has(x))
);
console.log(difference); // Set { 1, 2 }Visual:
setA: { 1, 2, 3, 4 }
setB: { 3, 4, 5, 6 }
↓
Difference: { 1, 2 } ← Only in A, not in BWhy This Is Perfect for Tracking Dependencies
The Reactivity Problem
In reactive systems, we need to track which effects depend on which properties.
const state = state({ count: 0, name: 'Alice' });
// Effect 1 depends on 'count'
effect(() => {
console.log(state.count);
});
// Effect 2 depends on 'count'
effect(() => {
document.title = state.count;
});
// Effect 3 depends on 'name'
effect(() => {
console.log(state.name);
});We need to track:
'count' → [effect1, effect2]
'name' → [effect3]Bad Solution: Using Arrays ❌
const dependencies = {
count: [],
name: []
};
// Track effect1 on 'count'
dependencies.count.push(effect1);
// Effect1 runs again (user scrolled, component re-rendered, etc.)
dependencies.count.push(effect1); // Duplicate!
// Effect1 runs again
dependencies.count.push(effect1); // Another duplicate!
console.log(dependencies.count);
// [effect1, effect1, effect1] 😱
// When count changes:
state.count = 5;
dependencies.count.forEach(fn => fn());
// effect1 runs 3 times! 🐛Problems:
- ❌ Duplicates cause effects to run multiple times
- ❌ Memory waste
- ❌ Performance issues
- ❌ Hard to check if already tracked
Good Solution: Using Sets ✅
const dependencies = {
count: new Set(),
name: new Set()
};
// Track effect1 on 'count'
dependencies.count.add(effect1);
// Effect1 runs again
dependencies.count.add(effect1); // Ignored - already in set!
// Effect1 runs again
dependencies.count.add(effect1); // Ignored - already in set!
console.log(dependencies.count.size);
// 1 ✨
// When count changes:
state.count = 5;
dependencies.count.forEach(fn => fn());
// effect1 runs exactly once! ✅Benefits:
- ✅ Each effect tracked exactly once
- ✅ No duplicates
- ✅ Automatic deduplication
- ✅ Fast lookups with
.has()
How It's Used in DOM Helpers Reactive
const reactiveMap = new WeakMap();
function createReactive(target) {
const deps = new Map(); // property → Set of effects
const proxy = new Proxy(target, {
get(obj, key) {
// Track: current effect depends on this property
if (currentEffect) {
// Create Set for this property if needed
if (!deps.has(key)) {
deps.set(key, new Set()); // ← Set for unique effects!
}
// Add effect to Set (duplicates ignored)
deps.get(key).add(currentEffect); // ← Automatic deduplication!
}
return obj[key];
},
set(obj, key, value) {
obj[key] = value;
// Trigger: Run all effects that depend on this property
const effects = deps.get(key);
if (effects) {
effects.forEach(effect => effect()); // ← Each runs once!
}
return true;
}
});
reactiveMap.set(proxy, { deps });
return proxy;
}The Flow Visualized
1. Create State
↓
state({ count: 0 })
↓
deps: Map {
'count' → Set() ← Empty Set, ready for effects
}
2. Run Effect 1
↓
effect(() => console.log(state.count))
↓
Reading state.count
↓
deps: Map {
'count' → Set(effect1) ← effect1 added
}
3. Run Effect 1 AGAIN
↓
effect(() => console.log(state.count))
↓
Reading state.count
↓
deps: Map {
'count' → Set(effect1) ← Still just effect1! No duplicate!
}
4. Change state.count
↓
state.count = 5
↓
Trigger effects for 'count'
↓
deps.get('count').forEach(fn => fn())
↓
effect1 runs ONCE ✅Common Questions
Q: "What can I store in a Set?"
Answer: Anything! But each item is unique.
// Numbers
const numbers = new Set([1, 2, 3, 2, 1]);
console.log(numbers); // Set { 1, 2, 3 }
// Strings
const words = new Set(['hello', 'world', 'hello']);
console.log(words); // Set { 'hello', 'world' }
// Objects (compared by reference!)
const obj1 = { id: 1 };
const obj2 = { id: 2 };
const obj3 = { id: 1 }; // Different object, same content
const objects = new Set([obj1, obj2, obj1, obj3]);
console.log(objects.size); // 3 (obj1, obj2, obj3 are all different!)
// Functions
const fn1 = () => {};
const fn2 = () => {};
const fns = new Set([fn1, fn2, fn1]);
console.log(fns.size); // 2Q: "How does Set check for duplicates?"
Answer: Using strict equality (===).
// Primitives (numbers, strings, booleans)
const set1 = new Set([1, '1', 1, true, 1]);
console.log(set1); // Set { 1, '1', true }
// 1 !== '1' !== true, so all are unique
// Objects (compared by reference)
const set2 = new Set([
{ id: 1 },
{ id: 1 },
{ id: 1 }
]);
console.log(set2.size); // 3
// Each {} is a different object in memory!
// Same reference
const obj = { id: 1 };
const set3 = new Set([obj, obj, obj]);
console.log(set3.size); // 1
// Same reference = same objectThink of it like this:
For primitives: Same value = duplicate
For objects: Same memory address = duplicateQ: "Is Set faster than Array for lookups?"
Yes! Much faster for checking existence.
// Array (slow for large lists)
const array = [1, 2, 3, ..., 10000];
array.includes(9999); // O(n) - checks every item
// Set (fast)
const set = new Set([1, 2, 3, ..., 10000]);
set.has(9999); // O(1) - instant lookup!Performance comparison:
| Operation | Array | Set |
|---|---|---|
| Add item | Fast | Fast |
| Check exists | Slow (scans all) | Very fast (instant) |
| Remove item | Slow (find + splice) | Fast |
| Get by index | Fast | Can't (no indexes) |
Rule of thumb:
- Use Array when you need order and indexes
- Use Set when you need uniqueness and fast lookups
Q: "Can I loop through a Set?"
Yes! Multiple ways.
const fruits = new Set(['apple', 'banana', 'orange']);
// Method 1: forEach
fruits.forEach(fruit => {
console.log(fruit);
});
// Method 2: for...of
for (const fruit of fruits) {
console.log(fruit);
}
// Method 3: Convert to array
const array = [...fruits];
array.forEach(fruit => console.log(fruit));
// Method 4: Using .values()
for (const fruit of fruits.values()) {
console.log(fruit);
}Note: Sets maintain insertion order, so items come out in the order they were added!
Q: "How do I convert between Set and Array?"
// Array → Set
const array = [1, 2, 3, 2, 1];
const set = new Set(array);
// Set → Array (Method 1: Spread)
const array1 = [...set];
// Set → Array (Method 2: Array.from)
const array2 = Array.from(set);
// Set → Array (Method 3: forEach + push)
const array3 = [];
set.forEach(item => array3.push(item));Practice Examples
Example 1: Tag System
Build a tag system where each item has unique tags.
class Article {
constructor(title) {
this.title = title;
this.tags = new Set(); // Unique tags only!
}
addTag(tag) {
this.tags.add(tag);
return this; // For chaining
}
removeTag(tag) {
this.tags.delete(tag);
return this;
}
hasTag(tag) {
return this.tags.has(tag);
}
getTags() {
return [...this.tags];
}
}
const article = new Article('How to JavaScript');
article
.addTag('javascript')
.addTag('tutorial')
.addTag('javascript') // Duplicate ignored!
.addTag('beginner')
.addTag('tutorial'); // Duplicate ignored!
console.log(article.getTags());
// ['javascript', 'tutorial', 'beginner']
console.log(article.hasTag('javascript')); // true
console.log(article.hasTag('advanced')); // falseExample 2: Event Listener System
Build a system where each event can have multiple unique listeners.
class EventEmitter {
constructor() {
this.events = new Map(); // event → Set of listeners
}
on(event, listener) {
if (!this.events.has(event)) {
this.events.set(event, new Set());
}
this.events.get(event).add(listener);
}
off(event, listener) {
if (this.events.has(event)) {
this.events.get(event).delete(listener);
}
}
emit(event, data) {
if (this.events.has(event)) {
this.events.get(event).forEach(listener => {
listener(data);
});
}
}
}
const emitter = new EventEmitter();
function onUserLogin(data) {
console.log('User logged in:', data.name);
}
// Register listener
emitter.on('login', onUserLogin);
emitter.on('login', onUserLogin); // Ignored - already registered!
// Emit event
emitter.emit('login', { name: 'Alice' });
// User logged in: Alice (only runs once!)Example 3: Dependency Tracker (Mini Reactivity)
Build a simple system that tracks and triggers dependencies.
const dependencies = new Map(); // property → Set of effects
let currentEffect = null;
function track(property) {
if (currentEffect) {
if (!dependencies.has(property)) {
dependencies.set(property, new Set());
}
dependencies.get(property).add(currentEffect);
}
}
function trigger(property) {
if (dependencies.has(property)) {
dependencies.get(property).forEach(effect => effect());
}
}
function effect(fn) {
currentEffect = fn;
fn(); // Run to track dependencies
currentEffect = null;
}
// Create reactive data
const data = { count: 0 };
// Effect 1
effect(() => {
track('count');
console.log('Effect 1:', data.count);
});
// Effect 1: 0
// Effect 2
effect(() => {
track('count');
console.log('Effect 2:', data.count * 2);
});
// Effect 2: 0
// Effect 2 runs AGAIN
effect(() => {
track('count');
console.log('Effect 2:', data.count * 2);
});
// Effect 2: 0
console.log('Tracked effects:', dependencies.get('count').size);
// 2 (not 3! Set prevented duplicate)
// Change data
data.count = 5;
trigger('count');
// Effect 1: 5
// Effect 2: 10
// Each runs once! ✅Example 4: Unique ID Generator
Track used IDs to prevent collisions.
class IDGenerator {
constructor() {
this.usedIDs = new Set();
}
generate() {
let id;
do {
id = Math.random().toString(36).substring(2, 9);
} while (this.usedIDs.has(id)); // Keep trying if duplicate
this.usedIDs.add(id);
return id;
}
release(id) {
this.usedIDs.delete(id);
}
isUsed(id) {
return this.usedIDs.has(id);
}
}
const generator = new IDGenerator();
const id1 = generator.generate(); // 'a3f8k2'
const id2 = generator.generate(); // 'x9m4p1'
const id3 = generator.generate(); // 'q7n2w5'
console.log(generator.isUsed(id1)); // true
generator.release(id1);
console.log(generator.isUsed(id1)); // falseSummary
What is a Set?
A collection that automatically ensures uniqueness - like a list where duplicates are impossible.
// Array allows duplicates
[1, 2, 2, 3] → [1, 2, 2, 3]
// Set removes duplicates
new Set([1, 2, 2, 3]) → Set { 1, 2, 3 }The Main Methods
const set = new Set();
set.add(item); // Add (duplicates ignored)
set.has(item); // Check if exists (fast!)
set.delete(item); // Remove
set.size; // Get count
set.clear(); // Remove all
set.forEach(fn); // Loop throughWhen to Use Set
✅ Use Set when:
- Need unique items only
- Fast existence checks
- Automatic deduplication
- Don't need indexes
❌ Use Array when:
- Duplicates are meaningful
- Need to access by index
- Need to sort frequently
- Order matters more than uniqueness
Why It's Perfect for Reactivity
Sets prevent duplicate effect registrations:
// Without Set (effects run multiple times)
const effects = [];
effects.push(effect1);
effects.push(effect1); // Duplicate!
effects.forEach(fn => fn()); // effect1 runs twice! ❌
// With Set (each effect runs once)
const effects = new Set();
effects.add(effect1);
effects.add(effect1); // Ignored!
effects.forEach(fn => fn()); // effect1 runs once! ✅Real Usage in DOM Helpers Reactive
// Track which effects depend on which properties
const deps = new Map();
// Property 'count' → Set of unique effects
deps.set('count', new Set());
// Add effects (duplicates automatically ignored)
deps.get('count').add(effect1);
deps.get('count').add(effect2);
deps.get('count').add(effect1); // Ignored!
// Trigger (each runs exactly once)
deps.get('count').forEach(fn => fn()); ✨Set is the perfect tool for tracking unique dependencies in reactive systems! 🎯