Set and Map provide efficient data storage with unique keys. Here's how to use them.
Set Basics#
1// Creating sets
2const set = new Set();
3const fromArray = new Set([1, 2, 3, 2, 1]); // {1, 2, 3}
4const fromString = new Set('hello'); // {'h', 'e', 'l', 'o'}
5
6// Adding values
7set.add(1);
8set.add(2);
9set.add(2); // Ignored, already exists
10set.add('hello');
11set.add({ id: 1 }); // Objects are unique by reference
12
13// Chaining
14set.add(1).add(2).add(3);
15
16// Checking membership
17console.log(set.has(1)); // true
18console.log(set.has(99)); // false
19
20// Size
21console.log(set.size); // number of elements
22
23// Removing
24set.delete(1); // Returns true if existed
25set.clear(); // Remove all
26
27// Iteration
28for (const value of set) {
29 console.log(value);
30}
31
32set.forEach((value) => {
33 console.log(value);
34});
35
36// Convert to array
37const arr = [...set];
38const arr2 = Array.from(set);Set Operations#
1// Union
2function union(setA, setB) {
3 return new Set([...setA, ...setB]);
4}
5
6// Intersection
7function intersection(setA, setB) {
8 return new Set([...setA].filter(x => setB.has(x)));
9}
10
11// Difference
12function difference(setA, setB) {
13 return new Set([...setA].filter(x => !setB.has(x)));
14}
15
16// Symmetric difference
17function symmetricDifference(setA, setB) {
18 return new Set(
19 [...setA].filter(x => !setB.has(x))
20 .concat([...setB].filter(x => !setA.has(x)))
21 );
22}
23
24// Subset check
25function isSubset(setA, setB) {
26 return [...setA].every(x => setB.has(x));
27}
28
29// Superset check
30function isSuperset(setA, setB) {
31 return [...setB].every(x => setA.has(x));
32}
33
34// Usage
35const a = new Set([1, 2, 3]);
36const b = new Set([2, 3, 4]);
37
38console.log(union(a, b)); // {1, 2, 3, 4}
39console.log(intersection(a, b)); // {2, 3}
40console.log(difference(a, b)); // {1}Map Basics#
1// Creating maps
2const map = new Map();
3const fromEntries = new Map([
4 ['key1', 'value1'],
5 ['key2', 'value2'],
6]);
7
8// Any type as key
9const objKey = { id: 1 };
10const funcKey = () => {};
11
12map.set('string', 'value');
13map.set(123, 'number key');
14map.set(objKey, 'object key');
15map.set(funcKey, 'function key');
16
17// Getting values
18console.log(map.get('string')); // 'value'
19console.log(map.get(objKey)); // 'object key'
20console.log(map.get({ id: 1 })); // undefined (different reference)
21
22// Checking keys
23console.log(map.has('string')); // true
24
25// Size
26console.log(map.size);
27
28// Removing
29map.delete('string');
30map.clear();
31
32// Chaining
33map.set('a', 1).set('b', 2).set('c', 3);
34
35// Iteration
36for (const [key, value] of map) {
37 console.log(key, value);
38}
39
40map.forEach((value, key) => {
41 console.log(key, value);
42});
43
44// Get keys, values, entries
45console.log([...map.keys()]);
46console.log([...map.values()]);
47console.log([...map.entries()]);Map vs Object#
1// Map advantages over objects
2
3// 1. Any key type
4const map = new Map();
5map.set({}, 'object key');
6map.set([], 'array key');
7map.set(NaN, 'NaN key'); // NaN works as key
8
9// 2. Maintains insertion order
10const ordered = new Map();
11ordered.set('z', 1);
12ordered.set('a', 2);
13ordered.set('m', 3);
14console.log([...ordered.keys()]); // ['z', 'a', 'm']
15
16// 3. Better performance for frequent additions/deletions
17const perfMap = new Map();
18for (let i = 0; i < 100000; i++) {
19 perfMap.set(i, i);
20}
21perfMap.delete(50000);
22
23// 4. Easy size check
24console.log(map.size); // vs Object.keys(obj).length
25
26// 5. No prototype pollution
27const safeMap = new Map();
28safeMap.set('constructor', 'safe');
29console.log(safeMap.get('constructor')); // 'safe'
30
31// Converting between Map and Object
32const obj = { a: 1, b: 2 };
33const mapFromObj = new Map(Object.entries(obj));
34const objFromMap = Object.fromEntries(map);WeakSet and WeakMap#
1// WeakSet - objects only, garbage collected
2const weakSet = new WeakSet();
3
4let obj = { data: 'value' };
5weakSet.add(obj);
6console.log(weakSet.has(obj)); // true
7
8obj = null; // Object can be garbage collected
9
10// Use case: tracking visited objects
11const visited = new WeakSet();
12
13function process(node) {
14 if (visited.has(node)) return;
15 visited.add(node);
16 // Process node
17}
18
19// WeakMap - object keys, garbage collected
20const weakMap = new WeakMap();
21
22let key = { id: 1 };
23weakMap.set(key, 'private data');
24console.log(weakMap.get(key)); // 'private data'
25
26key = null; // Entry can be garbage collected
27
28// Use case: private data
29const privateData = new WeakMap();
30
31class Person {
32 constructor(name, secret) {
33 privateData.set(this, { secret });
34 this.name = name;
35 }
36
37 getSecret() {
38 return privateData.get(this).secret;
39 }
40}
41
42// Use case: caching with automatic cleanup
43const cache = new WeakMap();
44
45function expensiveOperation(obj) {
46 if (cache.has(obj)) {
47 return cache.get(obj);
48 }
49
50 const result = /* expensive computation */;
51 cache.set(obj, result);
52 return result;
53}Practical Patterns#
1// 1. Counting occurrences
2function countOccurrences(arr) {
3 const counts = new Map();
4 for (const item of arr) {
5 counts.set(item, (counts.get(item) || 0) + 1);
6 }
7 return counts;
8}
9
10const letters = ['a', 'b', 'a', 'c', 'b', 'a'];
11console.log(countOccurrences(letters));
12// Map { 'a' => 3, 'b' => 2, 'c' => 1 }
13
14// 2. Grouping
15function groupBy(arr, keyFn) {
16 const groups = new Map();
17 for (const item of arr) {
18 const key = keyFn(item);
19 if (!groups.has(key)) {
20 groups.set(key, []);
21 }
22 groups.get(key).push(item);
23 }
24 return groups;
25}
26
27const users = [
28 { name: 'Alice', role: 'admin' },
29 { name: 'Bob', role: 'user' },
30 { name: 'Charlie', role: 'admin' },
31];
32
33console.log(groupBy(users, u => u.role));
34// Map { 'admin' => [{...}, {...}], 'user' => [{...}] }
35
36// 3. Unique values by property
37function uniqueBy(arr, keyFn) {
38 const seen = new Map();
39 for (const item of arr) {
40 const key = keyFn(item);
41 if (!seen.has(key)) {
42 seen.set(key, item);
43 }
44 }
45 return [...seen.values()];
46}
47
48// 4. LRU Cache
49class LRUCache {
50 constructor(capacity) {
51 this.capacity = capacity;
52 this.cache = new Map();
53 }
54
55 get(key) {
56 if (!this.cache.has(key)) return -1;
57
58 // Move to end (most recently used)
59 const value = this.cache.get(key);
60 this.cache.delete(key);
61 this.cache.set(key, value);
62 return value;
63 }
64
65 put(key, value) {
66 if (this.cache.has(key)) {
67 this.cache.delete(key);
68 } else if (this.cache.size >= this.capacity) {
69 // Remove oldest (first) entry
70 const oldestKey = this.cache.keys().next().value;
71 this.cache.delete(oldestKey);
72 }
73 this.cache.set(key, value);
74 }
75}
76
77// 5. Bidirectional map
78class BiMap {
79 constructor() {
80 this.forward = new Map();
81 this.reverse = new Map();
82 }
83
84 set(key, value) {
85 this.forward.set(key, value);
86 this.reverse.set(value, key);
87 }
88
89 getByKey(key) {
90 return this.forward.get(key);
91 }
92
93 getByValue(value) {
94 return this.reverse.get(value);
95 }
96}Set for Array Operations#
1// Remove duplicates
2const arr = [1, 2, 2, 3, 3, 3];
3const unique = [...new Set(arr)];
4
5// Check if array has duplicates
6function hasDuplicates(arr) {
7 return new Set(arr).size !== arr.length;
8}
9
10// Find duplicates
11function findDuplicates(arr) {
12 const seen = new Set();
13 const duplicates = new Set();
14
15 for (const item of arr) {
16 if (seen.has(item)) {
17 duplicates.add(item);
18 }
19 seen.add(item);
20 }
21
22 return [...duplicates];
23}
24
25// Filter unique objects by property
26function uniqueObjects(arr, key) {
27 const seen = new Set();
28 return arr.filter(obj => {
29 const value = obj[key];
30 if (seen.has(value)) return false;
31 seen.add(value);
32 return true;
33 });
34}
35
36// Check array equality (unordered)
37function arraysEqual(a, b) {
38 if (a.length !== b.length) return false;
39 const setB = new Set(b);
40 return a.every(item => setB.has(item));
41}Map for Memoization#
1// Simple memoization
2function memoize(fn) {
3 const cache = new Map();
4
5 return function(...args) {
6 const key = JSON.stringify(args);
7 if (cache.has(key)) {
8 return cache.get(key);
9 }
10
11 const result = fn.apply(this, args);
12 cache.set(key, result);
13 return result;
14 };
15}
16
17// With max size
18function memoizeWithLimit(fn, maxSize = 100) {
19 const cache = new Map();
20
21 return function(...args) {
22 const key = JSON.stringify(args);
23
24 if (cache.has(key)) {
25 const value = cache.get(key);
26 // Move to end
27 cache.delete(key);
28 cache.set(key, value);
29 return value;
30 }
31
32 const result = fn.apply(this, args);
33
34 if (cache.size >= maxSize) {
35 const firstKey = cache.keys().next().value;
36 cache.delete(firstKey);
37 }
38
39 cache.set(key, result);
40 return result;
41 };
42}
43
44// Async memoization
45function memoizeAsync(fn) {
46 const cache = new Map();
47
48 return async function(...args) {
49 const key = JSON.stringify(args);
50
51 if (cache.has(key)) {
52 return cache.get(key);
53 }
54
55 const promise = fn.apply(this, args);
56 cache.set(key, promise);
57
58 try {
59 return await promise;
60 } catch (error) {
61 cache.delete(key);
62 throw error;
63 }
64 };
65}Best Practices#
When to Use Set:
✓ Need unique values
✓ Fast membership testing
✓ Set operations (union, intersection)
✓ Removing duplicates
When to Use Map:
✓ Key-value pairs with non-string keys
✓ Need insertion order
✓ Frequent additions/deletions
✓ Need size property
When to Use WeakMap/WeakSet:
✓ Object references as keys
✓ Need garbage collection
✓ Storing private data
✓ Caching computed values
Performance:
✓ O(1) for add, delete, has operations
✓ Use Maps over objects for dynamic keys
✓ Consider WeakMap for large datasets
✓ Avoid JSON.stringify for complex keys
Conclusion#
Set and Map provide efficient, purpose-built data structures for unique values and key-value pairs. Use Set for uniqueness and membership testing, Map for flexible key types and ordered entries. WeakSet and WeakMap enable garbage collection for object-keyed data.