Map and Set Collections
Working with Map and Set data structures for better data organization
Challenge
Section titled “Challenge”You need to store unique user IDs and maintain insertion order, while also mapping user IDs to their profiles. Why are Map and Set better than objects for this use case?
Explanation
Section titled “Explanation”Map and Set are specialized collections that fix limitations of using objects for data storage. Maps maintain insertion order and accept any type as key, while Sets store unique values efficiently.
Set Basics
Section titled “Set Basics”// Remove duplicates from arrayconst numbers = [1, 2, 2, 3, 3, 3, 4];const unique = [...new Set(numbers)];console.log(unique); // [1, 2, 3, 4]
// Check membershipconst hasVisited = new Set();hasVisited.add('page1');hasVisited.add('page2');console.log(hasVisited.has('page1')); // true
// Iterate over Setconst colors = new Set(['red', 'green', 'blue']);colors.forEach(color => console.log(color));
// Set sizeconsole.log(colors.size); // 3
// Set operationsconst set1 = new Set([1, 2, 3]);const set2 = new Set([2, 3, 4]);
// Unionconst union = new Set([...set1, ...set2]);console.log(union); // Set(4) { 1, 2, 3, 4 }
// Intersectionconst intersection = new Set([...set1].filter(x => set2.has(x)));console.log(intersection); // Set(2) { 2, 3 }
// Differenceconst difference = new Set([...set1].filter(x => !set2.has(x)));console.log(difference); // Set(1) { 1 }Map Basics
Section titled “Map Basics”// Map is better than object for key-value pairsconst userMap = new Map();
// Any type can be a keyuserMap.set(1, { name: 'Alice' });userMap.set('userId_2', { name: 'Bob' });userMap.set({ id: 3 }, { name: 'Charlie' });
// Access valuesconsole.log(userMap.get(1)); // { name: 'Alice' }console.log(userMap.has('userId_2')); // true
// Map maintains insertion orderfor (const [key, value] of userMap) { console.log(key, value);}
// Map sizeconsole.log(userMap.size); // 3
// Delete entriesuserMap.delete(1);console.log(userMap.size); // 2
// Clear alluserMap.clear();console.log(userMap.size); // 0Solution: User Profiles Tracking
Section titled “Solution: User Profiles Tracking”// Track user profiles with Mapclass UserProfileManager { constructor() { this.profiles = new Map(); this.visitHistory = new Map(); }
addProfile(userId, profile) { this.profiles.set(userId, profile); this.visitHistory.set(userId, new Set()); }
recordVisit(userId, page) { if (this.visitHistory.has(userId)) { this.visitHistory.get(userId).add(page); } }
getVisitedPages(userId) { return Array.from(this.visitHistory.get(userId) || new Set()); }
getUniqueVisitors() { return this.profiles.size; }}
const manager = new UserProfileManager();manager.addProfile(1, { name: 'Alice', email: 'alice@example.com' });manager.addProfile(2, { name: 'Bob', email: 'bob@example.com' });
manager.recordVisit(1, 'home');manager.recordVisit(1, 'about');manager.recordVisit(1, 'home'); // Duplicate not added to Set
console.log(manager.getVisitedPages(1)); // ['home', 'about']console.log(manager.getUniqueVisitors()); // 2Map vs Object
Section titled “Map vs Object”// ❌ Object limitationsconst objMap = {};objMap['key'] = 'value1';objMap[123] = 'value2'; // Converts to string '123'objMap[{ id: 1 }] = 'value3'; // Converts to '[object Object]'
console.log(Object.keys(objMap)); // ['key', '123', '[object Object]']
// ✅ Map advantagesconst properMap = new Map();properMap.set('key', 'value1');properMap.set(123, 'value2');properMap.set({ id: 1 }, 'value3');
console.log(properMap.size); // 3console.log(properMap.get(123)); // 'value2'
// Only own enumerable properties with for...infor (let key in objMap) { console.log(key); // Includes prototype properties!}
// Only values with for...offor (const [key, value] of properMap) { console.log(key, value); // Only Map entries}WeakMap and WeakSet
Section titled “WeakMap and WeakSet”// WeakMap: Keys must be objects, not enumerable, keys are weakly referencedconst weakMap = new WeakMap();
const obj1 = { id: 1 };const obj2 = { id: 2 };
weakMap.set(obj1, 'metadata1');weakMap.set(obj2, 'metadata2');
console.log(weakMap.get(obj1)); // 'metadata1'
// When obj1 is garbage collected, its entry is automatically removed// Use case: Private object metadata
// WeakSet: Similar to Set but only stores objects weaklyconst weakSet = new WeakSet();weakSet.add(obj1);weakSet.add(obj2);console.log(weakSet.has(obj1)); // true
// Practical example: Tracking processed objects without memory leaksconst processedObjects = new WeakSet();
function processObject(obj) { if (processedObjects.has(obj)) { return; // Already processed }
// Process... processedObjects.add(obj);}Performance Considerations
Section titled “Performance Considerations”// Set lookup is O(1), array indexOf is O(n)const largeSet = new Set(Array.from({ length: 1000000 }, (_, i) => i));const largeArray = Array.from({ length: 1000000 }, (_, i) => i);
// Set lookupconsole.time('set');for (let i = 0; i < 10000; i++) { largeSet.has(Math.random() * 1000000);}console.timeEnd('set'); // Fast
// Array lookupconsole.time('array');for (let i = 0; i < 10000; i++) { largeArray.includes(Math.random() * 1000000);}console.timeEnd('array'); // SlowerPractical Example: Cache with Expiry
Section titled “Practical Example: Cache with Expiry”class ExpiringCache { constructor(ttl = 60000) { this.cache = new Map(); this.ttl = ttl; }
set(key, value) { this.cache.set(key, { value, expiresAt: Date.now() + this.ttl }); }
get(key) { const entry = this.cache.get(key); if (!entry) return undefined;
if (Date.now() > entry.expiresAt) { this.cache.delete(key); return undefined; }
return entry.value; }
clear() { this.cache.clear(); }}
const cache = new ExpiringCache(5000);cache.set('user_1', { name: 'Alice' });console.log(cache.get('user_1')); // { name: 'Alice' }
setTimeout(() => { console.log(cache.get('user_1')); // undefined (expired)}, 6000);