Skip to content
Plurafy

Map and Set Collections

Working with Map and Set data structures for better data organization

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?

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.

// Remove duplicates from array
const numbers = [1, 2, 2, 3, 3, 3, 4];
const unique = [...new Set(numbers)];
console.log(unique); // [1, 2, 3, 4]
// Check membership
const hasVisited = new Set();
hasVisited.add('page1');
hasVisited.add('page2');
console.log(hasVisited.has('page1')); // true
// Iterate over Set
const colors = new Set(['red', 'green', 'blue']);
colors.forEach(color => console.log(color));
// Set size
console.log(colors.size); // 3
// Set operations
const set1 = new Set([1, 2, 3]);
const set2 = new Set([2, 3, 4]);
// Union
const union = new Set([...set1, ...set2]);
console.log(union); // Set(4) { 1, 2, 3, 4 }
// Intersection
const intersection = new Set([...set1].filter(x => set2.has(x)));
console.log(intersection); // Set(2) { 2, 3 }
// Difference
const difference = new Set([...set1].filter(x => !set2.has(x)));
console.log(difference); // Set(1) { 1 }
// Map is better than object for key-value pairs
const userMap = new Map();
// Any type can be a key
userMap.set(1, { name: 'Alice' });
userMap.set('userId_2', { name: 'Bob' });
userMap.set({ id: 3 }, { name: 'Charlie' });
// Access values
console.log(userMap.get(1)); // { name: 'Alice' }
console.log(userMap.has('userId_2')); // true
// Map maintains insertion order
for (const [key, value] of userMap) {
console.log(key, value);
}
// Map size
console.log(userMap.size); // 3
// Delete entries
userMap.delete(1);
console.log(userMap.size); // 2
// Clear all
userMap.clear();
console.log(userMap.size); // 0
// Track user profiles with Map
class 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()); // 2
// ❌ Object limitations
const 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 advantages
const properMap = new Map();
properMap.set('key', 'value1');
properMap.set(123, 'value2');
properMap.set({ id: 1 }, 'value3');
console.log(properMap.size); // 3
console.log(properMap.get(123)); // 'value2'
// Only own enumerable properties with for...in
for (let key in objMap) {
console.log(key); // Includes prototype properties!
}
// Only values with for...of
for (const [key, value] of properMap) {
console.log(key, value); // Only Map entries
}
// WeakMap: Keys must be objects, not enumerable, keys are weakly referenced
const 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 weakly
const weakSet = new WeakSet();
weakSet.add(obj1);
weakSet.add(obj2);
console.log(weakSet.has(obj1)); // true
// Practical example: Tracking processed objects without memory leaks
const processedObjects = new WeakSet();
function processObject(obj) {
if (processedObjects.has(obj)) {
return; // Already processed
}
// Process...
processedObjects.add(obj);
}
// 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 lookup
console.time('set');
for (let i = 0; i < 10000; i++) {
largeSet.has(Math.random() * 1000000);
}
console.timeEnd('set'); // Fast
// Array lookup
console.time('array');
for (let i = 0; i < 10000; i++) {
largeArray.includes(Math.random() * 1000000);
}
console.timeEnd('array'); // Slower
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);