Table of Contents
- What is a Set?
- What is a Map?
- Set vs. Map: When to Use Which?
- Beyond Basic: WeakSet and WeakMap
- Common Pitfalls and Best Practices
- Conclusion
- References
What is a Set?
A Set is a collection of unique values. Unlike arrays, Sets do not allow duplicate elements, and they preserve the order of insertion.
Characteristics of Set
- No Duplicates: Adding the same value multiple times has no effect (uses
SameValueZeroequality for comparison). - Insertion Order: Elements are stored in the order they were added, making iteration predictable.
- No Indexes: Unlike arrays, Sets do not use numeric indexes. You cannot access elements via
set[0]; you must iterate or use methods likehas(). - Iterable: Sets are iterable, so you can loop over them with
for...oforforEach.
Creating a Set
To create a Set, use the Set constructor. You can initialize it with an iterable (e.g., an array) to pre-populate values:
// Empty Set
const emptySet = new Set();
// Set from an array (duplicates are automatically removed)
const numberSet = new Set([1, 2, 3, 2, 4]);
console.log(numberSet); // Set(4) { 1, 2, 3, 4 } (duplicate 2 is removed)
// Set with mixed data types (values can be any type)
const mixedSet = new Set([1, "hello", true, { key: "value" }, null]);
console.log(mixedSet.size); // 5 (all values are unique)
Set Methods and Properties
Sets provide a range of methods to manipulate and query data. Here are the most common:
| Method/Property | Description |
|---|---|
size | Returns the number of elements in the Set. |
add(value) | Adds value to the Set and returns the updated Set (chainable). |
delete(value) | Removes value from the Set; returns true if removed, false otherwise. |
has(value) | Returns true if value exists in the Set, false otherwise. |
clear() | Removes all elements from the Set. |
keys() | Returns an iterator for the values (same as values() for Sets). |
values() | Returns an iterator for the values in insertion order. |
entries() | Returns an iterator of [value, value] pairs (for consistency with Map). |
forEach(callback) | Executes callback for each value in insertion order. |
Examples of Methods:
const fruits = new Set();
// Add elements (chainable)
fruits.add("apple").add("banana").add("cherry");
console.log(fruits); // Set(3) { "apple", "banana", "cherry" }
// Check if an element exists
console.log(fruits.has("banana")); // true
console.log(fruits.has("grape")); // false
// Delete an element
fruits.delete("banana");
console.log(fruits); // Set(2) { "apple", "cherry" }
// Get size
console.log(fruits.size); // 2
// Clear all elements
fruits.clear();
console.log(fruits.size); // 0
Iterating Over a Set
Since Sets are iterable, you can loop over their values using:
1. for...of Loop
const colors = new Set(["red", "green", "blue"]);
for (const color of colors) {
console.log(color); // "red", "green", "blue" (in insertion order)
}
2. forEach Method
colors.forEach((value, valueAgain, set) => {
console.log(`Value: ${value}`); // "Value: red", "Value: green", "Value: blue"
// Note: The second parameter is the same as the first (for consistency with Map)
});
3. values(), keys(), or entries()
Since Sets have no keys, keys() and values() return the same iterator. entries() returns [value, value] pairs:
const iterator = colors.values();
console.log(iterator.next().value); // "red"
console.log(iterator.next().value); // "green"
// Convert Set to array using spread operator
const colorArray = [...colors];
console.log(colorArray); // ["red", "green", "blue"]
Use Cases for Set
-
Removing Duplicates: Easily deduplicate arrays by converting them to a Set and back to an array:
const duplicates = [1, 2, 2, 3, 3, 3]; const unique = [...new Set(duplicates)]; console.log(unique); // [1, 2, 3] -
Membership Testing: Check if a value exists (faster than
Array.includes()for large datasets). -
Storing Unique Objects: While objects are compared by reference, Sets ensure only one instance of an object is stored (if added multiple times).
What is a Map?
A Map is a collection of key-value pairs, where keys can be of any type (primitives, objects, functions, etc.). Unlike objects, Maps preserve insertion order and allow flexible key types.
Characteristics of Map
- Key Flexibility: Keys can be strings, numbers, booleans,
null,undefined, objects, or even other Maps. - Insertion Order: Key-value pairs are stored in the order they were added, making iteration predictable.
- No Coercion: Unlike objects, keys are not coerced to strings (e.g.,
1and"1"are distinct keys). - Iterable: Maps are iterable, so you can loop over key-value pairs directly.
Map vs. Objects: Key Differences
| Feature | Map | Object |
|---|---|---|
| Key Types | Any type (primitives, objects, etc.) | Only strings, Symbols, or numbers (coerced to strings). |
| Insertion Order | Preserved | Not guaranteed (prior to ES6; now partially preserved in some engines). |
| Size | size property (O(1)) | Must manually count keys (O(n)). |
| Iteration | Directly iterable (keys/values/entries) | Requires Object.keys(), Object.values(), etc. |
| Default Keys | None (only user-defined keys) | Inherits prototype keys (e.g., toString). |
Creating a Map
Use the Map constructor, optionally initializing with an iterable of [key, value] pairs (e.g., an array of arrays):
// Empty Map
const emptyMap = new Map();
// Map from an array of [key, value] pairs
const userMap = new Map([
["name", "Alice"],
[25, "age"], // Number key
[true, "isStudent"], // Boolean key
[{ id: 1 }, "userObject"] // Object key
]);
console.log(userMap.size); // 4 (all keys are unique)
Map Methods and Properties
Maps provide methods to manage key-value pairs. Here are the essentials:
| Method/Property | Description |
|---|---|
size | Returns the number of key-value pairs. |
set(key, value) | Adds/updates key with value; returns the Map (chainable). |
get(key) | Returns the value for key, or undefined if key does not exist. |
has(key) | Returns true if key exists, false otherwise. |
delete(key) | Removes key and its value; returns true if removed, false otherwise. |
clear() | Removes all key-value pairs. |
keys() | Returns an iterator for keys in insertion order. |
values() | Returns an iterator for values in insertion order. |
entries() | Returns an iterator of [key, value] pairs in insertion order. |
forEach(callback) | Executes callback for each [key, value] pair in insertion order. |
Examples of Methods:
const scores = new Map();
// Set key-value pairs (chainable)
scores.set("Alice", 95)
.set("Bob", 85)
.set("Charlie", 90);
// Get a value
console.log(scores.get("Bob")); // 85
console.log(scores.get("Dave")); // undefined (key does not exist)
// Check if a key exists
console.log(scores.has("Alice")); // true
// Delete a key
scores.delete("Charlie");
console.log(scores); // Map(2) { "Alice" => 95, "Bob" => 85 }
// Get size
console.log(scores.size); // 2
// Clear all pairs
scores.clear();
console.log(scores.size); // 0
Iterating Over a Map
Maps are iterable, so you can loop over key-value pairs using:
1. for...of Loop (over entries())
const bookMap = new Map([
["title", "JavaScript Basics"],
["author", "Jane Doe"],
["pages", 300]
]);
// Iterate over [key, value] pairs (default for Map)
for (const [key, value] of bookMap) {
console.log(`${key}: ${value}`);
// "title: JavaScript Basics", "author: Jane Doe", "pages: 300"
}
2. forEach Method
bookMap.forEach((value, key, map) => {
console.log(`${key} → ${value}`);
// "title → JavaScript Basics", "author → Jane Doe", "pages → 300"
});
3. keys(), values(), or entries()
// Iterate over keys
for (const key of bookMap.keys()) {
console.log(key); // "title", "author", "pages"
}
// Iterate over values
for (const value of bookMap.values()) {
console.log(value); // "JavaScript Basics", "Jane Doe", 300
}
Use Cases for Map
- Dynamic Key Types: When keys are not strings (e.g., objects, numbers, or functions).
- Frequent Additions/Deletions: Maps are optimized for dynamic updates (faster than objects for large datasets).
- Ordered Key-Value Pairs: When insertion order matters (e.g., logging, caching).
- Avoiding Prototype Pollution: Maps have no inherited keys, so you won’t accidentally overwrite methods like
toString.
Set vs. Map: When to Use Which?
- Use Set when you need a collection of unique values (no keys) and only care about membership (e.g., deduplication, tracking unique items).
- Use Map when you need key-value associations (e.g., storing metadata, caching, or mapping identifiers to data).
Beyond Basic: WeakSet and WeakMap
ES6 also introduced WeakSet and WeakMap, which are “weak” versions of Set and Map. They hold weak references to their elements, allowing garbage collection (GC) to remove elements when no other references exist. This prevents memory leaks.
WeakSet: Weak References for Unique Objects
A WeakSet is similar to a Set but with key differences:
- Only Objects: Keys must be objects (primitives throw an error).
- No Iteration: No
sizeproperty or iteration methods (keys(),values(), etc.), as elements can be garbage-collected unpredictably. - Weak References: If an object in a WeakSet has no other references, GC will remove it from the WeakSet automatically.
Use Cases for WeakSet:
- Tracking Unique Objects: Ensure an object is only processed once (e.g., marking DOM elements as “visited”).
- Avoiding Memory Leaks: Storing temporary object metadata without preventing GC.
Example:
const weakSet = new WeakSet();
let obj = { id: 1 };
weakSet.add(obj);
console.log(weakSet.has(obj)); // true
// Remove reference to obj
obj = null;
// Now, obj is eligible for GC, and will be removed from weakSet automatically.
WeakMap: Associating Data Without Memory Leaks
A WeakMap is similar to a Map but with weak references to keys:
- Keys Must Be Objects: Primitives are not allowed as keys.
- No Iteration: No
sizeproperty or iteration methods (keys can be GC’d). - Weak Key References: If a key object has no other references, the entire entry is removed from the WeakMap.
Use Cases for WeakMap:
- Private Data: Associate “private” data with an object (e.g., caching results for an object without memory leaks).
- DOM Element Metadata: Store data for DOM elements (e.g., event listeners) that should be cleaned up when the element is removed.
Example:
const weakMap = new WeakMap();
let user = { name: "Alice" };
weakMap.set(user, { age: 30 }); // Associate age with user
console.log(weakMap.get(user)); // { age: 30 }
// Remove reference to user
user = null;
// user is now GC’d, and the WeakMap entry is automatically removed.
Common Pitfalls and Best Practices
-
Equality in Sets: Sets use
SameValueZeroequality:5and"5"are distinct.NaNis considered equal to itself (unlike===, which returnsfalseforNaN === NaN).
const set = new Set([NaN, NaN]); console.log(set.size); // 1 (only one NaN) -
Object Keys in Maps: Objects are compared by reference, not value. Two distinct objects with the same content are treated as different keys:
const map = new Map(); map.set({ id: 1 }, "value"); console.log(map.get({ id: 1 })); // undefined (different object reference) -
WeakSet/WeakMap Limitations: No iteration or
size—use them only when you don’t need to track or loop over elements.
Conclusion
Set and Map are powerful additions to JavaScript’s data structure toolkit, addressing longstanding limitations of arrays and objects. Use Set for unique values and membership testing, and Map for flexible key-value pairs. For memory-sensitive scenarios (e.g., temporary object metadata), WeakSet and WeakMap prevent leaks by leveraging weak references.
By choosing the right structure for your use case, you’ll write cleaner, more efficient code—and avoid common pitfalls like duplicate values or memory bloat.