Back to Blog
JavaScriptMapSetCollections

JavaScript Map and Set Guide

Master JavaScript Map and Set collections for efficient data storage and manipulation.

B
Bootspring Team
Engineering
October 27, 2018
7 min read

Map and Set are ES6 collections that provide efficient alternatives to objects and arrays. Here's how to use them effectively.

Map Basics#

1// Create a Map 2const map = new Map(); 3 4// Set values 5map.set('name', 'John'); 6map.set('age', 30); 7map.set(123, 'number key'); 8 9// Get values 10map.get('name'); // 'John' 11map.get('missing'); // undefined 12 13// Check existence 14map.has('name'); // true 15map.has('missing'); // false 16 17// Get size 18map.size; // 3 19 20// Delete 21map.delete('age'); // true (if existed) 22 23// Clear all 24map.clear(); 25 26// Initialize with entries 27const userMap = new Map([ 28 ['id', 1], 29 ['name', 'John'], 30 ['email', 'john@example.com'] 31]);

Map Keys#

1// Any value can be a key 2const map = new Map(); 3 4// Object keys 5const objKey = { id: 1 }; 6map.set(objKey, 'object value'); 7map.get(objKey); // 'object value' 8 9// Function keys 10const fnKey = () => {}; 11map.set(fnKey, 'function value'); 12 13// DOM element keys 14const element = document.getElementById('app'); 15map.set(element, { clicks: 0 }); 16 17// Keys maintain reference equality 18const obj1 = { id: 1 }; 19const obj2 = { id: 1 }; 20map.set(obj1, 'first'); 21map.set(obj2, 'second'); 22map.get(obj1); // 'first' 23map.get(obj2); // 'second' 24map.size; // 2 (different references) 25 26// NaN as key (works unlike objects) 27map.set(NaN, 'not a number'); 28map.get(NaN); // 'not a number'

Map Iteration#

1const map = new Map([ 2 ['a', 1], 3 ['b', 2], 4 ['c', 3] 5]); 6 7// for...of (entries) 8for (const [key, value] of map) { 9 console.log(key, value); 10} 11 12// forEach 13map.forEach((value, key) => { 14 console.log(key, value); 15}); 16 17// Keys iterator 18for (const key of map.keys()) { 19 console.log(key); 20} 21 22// Values iterator 23for (const value of map.values()) { 24 console.log(value); 25} 26 27// Entries iterator 28for (const [key, value] of map.entries()) { 29 console.log(key, value); 30} 31 32// Convert to array 33const entries = [...map]; // [['a', 1], ['b', 2], ['c', 3]] 34const keys = [...map.keys()]; // ['a', 'b', 'c'] 35const values = [...map.values()]; // [1, 2, 3]

Map vs Object#

1// Map advantages over objects: 2 3// 1. Any key type 4const map = new Map(); 5map.set({}, 'object key'); 6map.set(42, 'number key'); 7map.set(true, 'boolean key'); 8 9// 2. Maintains insertion order 10const orderedMap = new Map([ 11 ['first', 1], 12 ['second', 2], 13 ['third', 3] 14]); 15// Iteration order guaranteed 16 17// 3. Better size tracking 18map.size; // O(1) property 19// vs Object.keys(obj).length 20 21// 4. Better performance for frequent add/delete 22 23// 5. No prototype pollution 24const map2 = new Map(); 25map2.get('toString'); // undefined 26// vs obj['toString'] returns function 27 28// When to use Object: 29// - JSON serialization needed 30// - Simple string keys 31// - Need object literal syntax

Set Basics#

1// Create a Set 2const set = new Set(); 3 4// Add values 5set.add(1); 6set.add(2); 7set.add(3); 8set.add(2); // Duplicate, ignored 9 10// Check existence 11set.has(1); // true 12set.has(4); // false 13 14// Get size 15set.size; // 3 16 17// Delete 18set.delete(2); // true 19 20// Clear all 21set.clear(); 22 23// Initialize with values 24const numbers = new Set([1, 2, 3, 2, 1]); 25// Set { 1, 2, 3 } 26 27// From string 28const chars = new Set('hello'); 29// Set { 'h', 'e', 'l', 'o' }

Set Operations#

1const setA = new Set([1, 2, 3, 4]); 2const setB = new Set([3, 4, 5, 6]); 3 4// Union 5const union = new Set([...setA, ...setB]); 6// Set { 1, 2, 3, 4, 5, 6 } 7 8// Intersection 9const intersection = new Set( 10 [...setA].filter(x => setB.has(x)) 11); 12// Set { 3, 4 } 13 14// Difference (A - B) 15const difference = new Set( 16 [...setA].filter(x => !setB.has(x)) 17); 18// Set { 1, 2 } 19 20// Symmetric difference 21const symmetricDiff = new Set( 22 [...setA].filter(x => !setB.has(x)).concat( 23 [...setB].filter(x => !setA.has(x)) 24 ) 25); 26// Set { 1, 2, 5, 6 } 27 28// Subset check 29const isSubset = [...setA].every(x => setB.has(x)); 30 31// Superset check 32const isSuperset = [...setB].every(x => setA.has(x));

Set Use Cases#

1// Remove duplicates from array 2const numbers = [1, 2, 2, 3, 3, 3, 4]; 3const unique = [...new Set(numbers)]; 4// [1, 2, 3, 4] 5 6// Remove duplicate objects (by property) 7const users = [ 8 { id: 1, name: 'John' }, 9 { id: 2, name: 'Jane' }, 10 { id: 1, name: 'John' } 11]; 12 13const uniqueUsers = [ 14 ...new Map(users.map(u => [u.id, u])).values() 15]; 16 17// Track unique values 18function countUniqueWords(text) { 19 const words = text.toLowerCase().match(/\w+/g) || []; 20 return new Set(words).size; 21} 22 23// Fast lookup 24const allowedIds = new Set([1, 2, 3, 4, 5]); 25 26function isAllowed(id) { 27 return allowedIds.has(id); // O(1) lookup 28}

WeakMap#

1// Keys must be objects 2// Keys are weakly referenced (can be garbage collected) 3const weakMap = new WeakMap(); 4 5let obj = { name: 'John' }; 6weakMap.set(obj, 'metadata'); 7 8weakMap.get(obj); // 'metadata' 9 10// When obj is garbage collected, entry is removed 11obj = null; 12 13// No iteration methods (keys can disappear anytime) 14// weakMap.keys() // Not available 15// weakMap.values() // Not available 16// weakMap.size // Not available 17 18// Use case: Private data 19const privateData = new WeakMap(); 20 21class User { 22 constructor(name) { 23 privateData.set(this, { name }); 24 } 25 26 getName() { 27 return privateData.get(this).name; 28 } 29} 30 31// Use case: Caching with automatic cleanup 32const cache = new WeakMap(); 33 34function getCachedResult(obj) { 35 if (cache.has(obj)) { 36 return cache.get(obj); 37 } 38 39 const result = expensiveComputation(obj); 40 cache.set(obj, result); 41 return result; 42}

WeakSet#

1// Values must be objects 2// Values are weakly referenced 3const weakSet = new WeakSet(); 4 5let obj = { id: 1 }; 6weakSet.add(obj); 7weakSet.has(obj); // true 8 9obj = null; 10// Entry will be garbage collected 11 12// Use case: Track objects without preventing GC 13const visitedNodes = new WeakSet(); 14 15function traverse(node) { 16 if (visitedNodes.has(node)) { 17 return; // Already visited 18 } 19 20 visitedNodes.add(node); 21 // Process node 22 node.children.forEach(traverse); 23} 24 25// Use case: Mark objects 26const processed = new WeakSet(); 27 28function processOnce(item) { 29 if (processed.has(item)) { 30 return; 31 } 32 33 processed.add(item); 34 // Process item 35}

Practical Patterns#

1// Memoization with Map 2function memoize(fn) { 3 const cache = new Map(); 4 5 return function(...args) { 6 const key = JSON.stringify(args); 7 8 if (cache.has(key)) { 9 return cache.get(key); 10 } 11 12 const result = fn.apply(this, args); 13 cache.set(key, result); 14 return result; 15 }; 16} 17 18// Group by with Map 19function groupBy(array, keyFn) { 20 const map = new Map(); 21 22 for (const item of array) { 23 const key = keyFn(item); 24 const group = map.get(key) || []; 25 group.push(item); 26 map.set(key, group); 27 } 28 29 return map; 30} 31 32const users = [ 33 { name: 'John', role: 'admin' }, 34 { name: 'Jane', role: 'user' }, 35 { name: 'Bob', role: 'admin' } 36]; 37 38const byRole = groupBy(users, u => u.role); 39// Map { 'admin' => [...], 'user' => [...] } 40 41// LRU Cache 42class LRUCache { 43 constructor(capacity) { 44 this.capacity = capacity; 45 this.cache = new Map(); 46 } 47 48 get(key) { 49 if (!this.cache.has(key)) return -1; 50 51 const value = this.cache.get(key); 52 this.cache.delete(key); 53 this.cache.set(key, value); 54 return value; 55 } 56 57 put(key, value) { 58 this.cache.delete(key); 59 this.cache.set(key, value); 60 61 if (this.cache.size > this.capacity) { 62 const firstKey = this.cache.keys().next().value; 63 this.cache.delete(firstKey); 64 } 65 } 66}

Converting Between Types#

1// Object to Map 2const obj = { a: 1, b: 2, c: 3 }; 3const map = new Map(Object.entries(obj)); 4 5// Map to Object 6const mapToObj = Object.fromEntries(map); 7 8// Array to Set 9const arr = [1, 2, 3]; 10const set = new Set(arr); 11 12// Set to Array 13const setToArr = [...set]; 14// or Array.from(set) 15 16// Map to Array of entries 17const mapToArr = [...map]; 18 19// JSON with Map 20const mapJson = JSON.stringify([...map]); 21const mapFromJson = new Map(JSON.parse(mapJson));

Best Practices#

Use Map When: ✓ Keys aren't strings/symbols ✓ Need to maintain insertion order ✓ Frequent additions/deletions ✓ Need map.size property Use Set When: ✓ Need unique values only ✓ Fast membership testing ✓ Set operations needed ✓ Deduplicating arrays Use WeakMap/WeakSet When: ✓ Keys are objects only ✓ Don't want to prevent GC ✓ Caching object metadata ✓ Private instance data Avoid: ✗ Map for simple string keys ✗ Set for ordered unique lists ✗ WeakMap when you need iteration ✗ Converting unnecessarily

Conclusion#

Map and Set provide powerful alternatives to objects and arrays for specific use cases. Map excels with non-string keys and maintains insertion order, while Set ensures uniqueness and enables set operations. WeakMap and WeakSet are perfect for caching and tracking objects without preventing garbage collection. Choose the right collection based on your data structure needs.

Share this article

Help spread the word about Bootspring