Back to Blog
JavaScriptSetMapData Structures

JavaScript Set and Map Methods

Master Set and Map data structures in JavaScript. From basic operations to advanced patterns.

B
Bootspring Team
Engineering
January 24, 2021
7 min read

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.

Share this article

Help spread the word about Bootspring