Sets - Unique Value Collections
ES6 introduced the Set object, a powerful data structure for storing unique values of any type. Sets automatically ensure that each value appears only once, making them perfect for removing duplicates and performing mathematical set operations.
What is a Set?
A Set is a collection of values where each value must be unique. Unlike arrays, Sets:
- Automatically remove duplicate values
- Can contain any type of value (primitives or objects)
- Maintain insertion order
- Provide efficient methods for checking value existence
Key Fact: Sets use the SameValueZero algorithm for equality checks, which treats NaN as equal to NaN (unlike === comparison).
Creating Sets
You can create Sets in multiple ways:
// Empty Set
const emptySet = new Set();
// Set from array
const numbers = new Set([1, 2, 3, 4, 5]);
// Set with duplicates removed automatically
const unique = new Set([1, 2, 2, 3, 3, 3, 4]);
console.log(unique); // Set {1, 2, 3, 4}
// Set from string (each character)
const letters = new Set('hello');
console.log(letters); // Set {'h', 'e', 'l', 'o'}
// Set with mixed types
const mixed = new Set([1, 'text', true, null, {id: 1}]);
Set Methods
Sets provide several methods for adding, checking, and removing values:
const fruits = new Set();
// add() - Add a value
fruits.add('apple');
fruits.add('banana');
fruits.add('orange');
fruits.add('apple'); // Duplicate, won't be added
console.log(fruits.size); // 3
// has() - Check if value exists
console.log(fruits.has('apple')); // true
console.log(fruits.has('grape')); // false
// delete() - Remove a value
fruits.delete('banana');
console.log(fruits.has('banana')); // false
// clear() - Remove all values
fruits.clear();
console.log(fruits.size); // 0
Tip: The add() method returns the Set itself, allowing for method chaining: set.add(1).add(2).add(3)
Iterating Over Sets
Sets are iterable and can be looped through in several ways:
const colors = new Set(['red', 'green', 'blue']);
// for...of loop
for (const color of colors) {
console.log(color);
}
// forEach method
colors.forEach((value, valueAgain, set) => {
console.log(value); // Note: value appears twice for consistency with Map
});
// Convert to array
const colorArray = [...colors];
const colorArray2 = Array.from(colors);
// Using iterator methods
console.log(colors.keys()); // SetIterator
console.log(colors.values()); // SetIterator
console.log(colors.entries()); // SetIterator of [value, value]
Removing Duplicates from Arrays
One of the most common uses of Sets is removing duplicate values from arrays:
// Remove duplicates
const numbers = [1, 2, 2, 3, 3, 3, 4, 4, 5];
const unique = [...new Set(numbers)];
console.log(unique); // [1, 2, 3, 4, 5]
// Remove duplicate strings
const words = ['hello', 'world', 'hello', 'javascript'];
const uniqueWords = [...new Set(words)];
console.log(uniqueWords); // ['hello', 'world', 'javascript']
// Remove duplicate objects (by reference)
const obj1 = {id: 1};
const obj2 = {id: 2};
const objects = [obj1, obj2, obj1, {id: 1}]; // Last one is different reference
const uniqueObjects = [...new Set(objects)];
console.log(uniqueObjects.length); // 3 (last {id: 1} is different object)
Important: Sets compare objects by reference, not by value. Two objects with identical properties are considered different unless they reference the same object.
Set Operations
Sets are perfect for mathematical set operations like union, intersection, and difference:
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);
// Union - All unique values from both sets
const union = new Set([...setA, ...setB]);
console.log(union); // Set {1, 2, 3, 4, 5, 6}
// Intersection - Values present in both sets
const intersection = new Set([...setA].filter(x => setB.has(x)));
console.log(intersection); // Set {3, 4}
// Difference - Values in setA but not in setB
const difference = new Set([...setA].filter(x => !setB.has(x)));
console.log(difference); // Set {1, 2}
// Symmetric Difference - Values in either set but not both
const symmetricDiff = new Set([
...[...setA].filter(x => !setB.has(x)),
...[...setB].filter(x => !setA.has(x))
]);
console.log(symmetricDiff); // Set {1, 2, 5, 6}
// Subset - Check if setA is subset of setB
const isSubset = [...setA].every(x => setB.has(x));
console.log(isSubset); // false
Practical Examples
// Example 1: Track unique visitors
const uniqueVisitors = new Set();
function trackVisitor(userId) {
uniqueVisitors.add(userId);
console.log(`Total unique visitors: ${uniqueVisitors.size}`);
}
trackVisitor('user1');
trackVisitor('user2');
trackVisitor('user1'); // Duplicate, won't increase count
// Example 2: Find unique tags
const articles = [
{ tags: ['javascript', 'programming', 'web'] },
{ tags: ['css', 'design', 'web'] },
{ tags: ['javascript', 'es6', 'programming'] }
];
const allTags = new Set();
articles.forEach(article => {
article.tags.forEach(tag => allTags.add(tag));
});
console.log([...allTags]);
// ['javascript', 'programming', 'web', 'css', 'design', 'es6']
// Example 3: Remove duplicates while maintaining case
function getUniqueCaseInsensitive(strings) {
const seen = new Set();
return strings.filter(str => {
const lower = str.toLowerCase();
if (seen.has(lower)) {
return false;
}
seen.add(lower);
return true;
});
}
const names = ['John', 'jane', 'JOHN', 'Jane', 'Bob'];
console.log(getUniqueCaseInsensitive(names)); // ['John', 'jane', 'Bob']
Performance Characteristics
Understanding Set performance helps you choose the right data structure:
Time Complexity:
- add(): O(1) - Constant time
- has(): O(1) - Constant time
- delete(): O(1) - Constant time
- clear(): O(n) - Linear time
- Size: O(1) - Constant time
Comparison with Arrays:
- Checking if value exists: Set.has() is O(1) vs Array.includes() is O(n)
- Adding unique values: Set.add() is O(1) vs Array.push() with duplicate check is O(n)
- Removing values: Set.delete() is O(1) vs Array.splice() is O(n)
Best Practice: Use Sets when you need to maintain unique values or frequently check for value existence. Use Arrays when you need indexed access or duplicate values.
Set vs Array Comparison
// When to use Set
const uniqueIds = new Set([1, 2, 3]); // Need unique values
uniqueIds.has(2); // Fast lookup - O(1)
uniqueIds.add(4); // Fast insertion - O(1)
// When to use Array
const scores = [95, 88, 95, 92]; // Duplicates are meaningful
scores[0]; // Need indexed access
scores.map(x => x * 1.1); // Need array methods
Practice Exercise:
Challenge: Create a function that finds common friends between two users.
function findCommonFriends(user1Friends, user2Friends) {
const set1 = new Set(user1Friends);
const commonFriends = user2Friends.filter(friend => set1.has(friend));
return commonFriends;
}
const alice = ['Bob', 'Charlie', 'David', 'Eve'];
const john = ['Charlie', 'Eve', 'Frank', 'Grace'];
console.log(findCommonFriends(alice, john));
// Output: ['Charlie', 'Eve']
Try it yourself: Extend this to find friends unique to each user (symmetric difference).
Summary
In this lesson, you learned:
- Sets store unique values and automatically remove duplicates
- Set methods: add(), has(), delete(), clear(), and size property
- Sets can be iterated with for...of, forEach, and spread operator
- Sets are perfect for removing array duplicates and mathematical operations
- Set operations: union, intersection, difference, and subset checks
- Sets provide O(1) performance for add, has, and delete operations
- Choose Sets for unique values and fast lookups, Arrays for indexed access
Next Up: In the next lesson, we'll explore Maps - key-value pairs with any type of key!