Skip to content

Sponsor

Sponsor

Set (The No-Duplicates List)

The Duplicate Problem

Imagine you're tracking which effects depend on a piece of state:

javascript
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! 🎯

javascript
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.

javascript
// 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 email

Problems:

  • ❌ 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

javascript
// ARRAY - Allows duplicates
const colors = ['red', 'blue', 'red', 'green', 'blue'];

console.log(colors);
// ['red', 'blue', 'red', 'green', 'blue']

console.log(colors.length); // 5
javascript
// SET - Removes duplicates automatically
const colors = new Set(['red', 'blue', 'red', 'green', 'blue']);

console.log(colors);
// Set { 'red', 'blue', 'green' }

console.log(colors.size); // 3

Side-by-Side Operations

OperationArraySet
Add itemarray.push('x')set.add('x')
Check if existsarray.includes('x') (slow)set.has('x') (fast)
Remove itemarray.splice(...) (complex)set.delete('x') (simple)
Get sizearray.lengthset.size
Clear allarray.length = 0set.clear()
Allows duplicates?✅ Yes❌ No

How Set Works

The Basic API

Set has 6 main methods:

javascript
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
// b

Creating Sets

javascript
// 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

javascript
const uniqueVisitors = new Set();

Think of this as creating a guest book where each name can only appear once.

Step 2: Add Visitors

javascript
// 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

javascript
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

javascript
// 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: Bob

Step 5: Remove a Visitor

javascript
// 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:

javascript
const numbers = [1, 2, 3, 2, 4, 1, 5, 3];

// Want: [1, 2, 3, 4, 5]

The Solution:

javascript
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:

javascript
// 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:

javascript
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:

javascript
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)

javascript
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 B

Why This Is Perfect for Tracking Dependencies

The Reactivity Problem

In reactive systems, we need to track which effects depend on which properties.

javascript
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

javascript
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

javascript
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

javascript
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.

javascript
// 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); // 2

Q: "How does Set check for duplicates?"

Answer: Using strict equality (===).

javascript
// 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 object

Think of it like this:

For primitives: Same value = duplicate
For objects:    Same memory address = duplicate

Q: "Is Set faster than Array for lookups?"

Yes! Much faster for checking existence.

javascript
// 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:

OperationArraySet
Add itemFastFast
Check existsSlow (scans all)Very fast (instant)
Remove itemSlow (find + splice)Fast
Get by indexFastCan'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.

javascript
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?"

javascript
// 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.

javascript
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')); // false

Example 2: Event Listener System

Build a system where each event can have multiple unique listeners.

javascript
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.

javascript
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.

javascript
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)); // false

Summary

What is a Set?

A collection that automatically ensures uniqueness - like a list where duplicates are impossible.

javascript
// 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

javascript
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 through

When 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:

javascript
// 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

javascript
// 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! 🎯