MinhVo

Minh Vo

rss feed

Slaying code & making it lit fr fr 🔥 tagline

Hey there 👋 I'm an AI Engineer with 7 years of experience building scalable web and mobile applications. Currently at Neurond AI (May 2025 — present), architecting an Enterprise AI Assistant Platform with multi-tenant RAG on pgvector, multi-provider LLM orchestration, and Azure-native infrastructure. Previously spent 5+ years at SNAPTEC (Sep 2019 — Apr 2025), leading SaaS themes, admin dashboards, and e-commerce platforms — earned the Hero of the Year award in 2021. I specialize in TypeScript, React, Next.js, and AI-Native engineering with Claude Code and Cursor.bio

Back to blogs

JavaScript Map and Set: Modern Data Structures

Master Map and Set: insertion order, key types, WeakMap, WeakSet, and performance.

JavaScriptData StructuresES6Collections

By MinhVo

Introduction

JavaScript's Map and Set are powerful collection types introduced in ES6 that solve fundamental limitations of plain objects and arrays. Map provides key-value storage with any type of key, maintains insertion order, and offers superior performance for frequent additions and deletions. Set stores unique values of any type, automatically preventing duplicates and providing efficient membership testing.

Understanding when and how to use Map and Set instead of objects and arrays is crucial for writing performant JavaScript. These collections are foundational to many algorithms and data processing patterns used in modern web applications. This comprehensive guide covers Map, Set, WeakMap, and WeakSet with practical examples and performance comparisons used in production applications.

Data Structures

Understanding Map

What is a Map?

A Map is a collection of key-value pairs where keys can be any type—objects, functions, primitives, or even other Maps. Unlike plain objects which coerce keys to strings, Maps preserve the original key type and maintain insertion order consistently across all operations.

Maps also track their size explicitly through a size property, provide built-in iteration methods through entries(), keys(), and values(), and offer optimized performance for frequent mutations. These characteristics make Maps ideal for cases where you need flexible key types, ordered data, or frequent additions and deletions.

The internal implementation of Maps uses hash tables with linked lists to maintain order, providing O(1) average case performance for get, set, has, and delete operations. This makes Maps significantly faster than objects for scenarios involving frequent key-value operations.

Map vs Object

Understanding the differences between Map and plain objects helps you choose the right data structure:

// Object limitations - keys are coerced to strings
const obj = {};
obj[1] = 'number key';
obj['1'] = 'string key';
console.log(obj[1]); // 'string key' - number coerced to string!
 
const funcKey = function() {};
obj[funcKey] = 'function key';
console.log(obj); // { '1': 'string key', '[object Object]': ... }
 
// Map preserves key types
const map = new Map();
map.set(1, 'number key');
map.set('1', 'string key');
console.log(map.get(1));   // 'number key'
console.log(map.get('1')); // 'string key' - different!
 
const funcKey2 = function() {};
map.set(funcKey2, 'function key');
console.log(map.get(funcKey2)); // 'function key'

Maps also excel at tracking size and iteration:

// Object size requires manual counting
const obj = { a: 1, b: 2, c: 3 };
console.log(Object.keys(obj).length); // 3
 
// Map has built-in size property
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
console.log(map.size); // 3

Map Methods

Maps provide a comprehensive API for managing key-value pairs:

const userMap = new Map();
 
// Setting values - returns the Map for chaining
userMap.set('alice', { name: 'Alice', age: 30 });
userMap.set('bob', { name: 'Bob', age: 25 });
userMap.set('charlie', { name: 'Charlie', age: 35 });
 
// Getting values - returns undefined for missing keys
const alice = userMap.get('alice'); // { name: 'Alice', age: 30 }
const unknown = userMap.get('dave'); // undefined
 
// Checking existence - O(1) operation
console.log(userMap.has('alice')); // true
console.log(userMap.has('dave'));  // false
 
// Size - O(1) operation
console.log(userMap.size); // 3
 
// Deleting - returns boolean indicating success
userMap.delete('bob');
console.log(userMap.size); // 2
 
// Clearing all entries
userMap.clear();
console.log(userMap.size); // 0

Map Iteration

Maps maintain insertion order and provide multiple iteration methods:

const scores = new Map([
    ['Alice', 95],
    ['Bob', 87],
    ['Charlie', 92],
    ['Diana', 88]
]);
 
// Iterate entries (default iterator)
for (const [name, score] of scores) {
    console.log(`${name}: ${score}`);
}
 
// Iterate keys only
for (const name of scores.keys()) {
    console.log(name);
}
 
// Iterate values only
for (const score of scores.values()) {
    console.log(score);
}
 
// forEach method
scores.forEach((score, name) => {
    console.log(`${name}: ${score}`);
});
 
// Convert to array
const entries = [...scores]; // [['Alice', 95], ['Bob', 87], ...]
const names = [...scores.keys()]; // ['Alice', 'Bob', 'Charlie', 'Diana']
const values = [...scores.values()]; // [95, 87, 92, 88]

Map from Arrays and Objects

// Create Map from array of pairs
const mapFromArray = new Map([
    ['key1', 'value1'],
    ['key2', 'value2'],
    ['key3', 'value3']
]);
 
// Create Map from object
const obj = { a: 1, b: 2, c: 3 };
const mapFromObj = new Map(Object.entries(obj));
 
// Create object from Map
const map = new Map([['x', 10], ['y', 20]]);
const objFromMap = Object.fromEntries(map);
// { x: 10, y: 20 }

Understanding Set

What is a Set?

A Set is a collection of unique values. It automatically prevents duplicate entries and provides efficient methods for adding, deleting, and checking membership. Sets can store any type of value, including objects and functions.

The primary use case for Sets is maintaining uniqueness. Whether you're tracking unique user IDs, removing duplicate values from arrays, or managing collections of distinct items, Sets provide a clean, efficient solution. Sets use hash tables internally, providing O(1) average case performance for add, delete, and has operations.

Set Methods

const uniqueNumbers = new Set();
 
// Adding values - returns the Set for chaining
uniqueNumbers.add(1);
uniqueNumbers.add(2);
uniqueNumbers.add(3);
uniqueNumbers.add(2); // Duplicate - ignored
uniqueNumbers.add(1); // Duplicate - ignored
 
console.log(uniqueNumbers.size); // 3
 
// Checking existence - O(1) operation
console.log(uniqueNumbers.has(2)); // true
console.log(uniqueNumbers.has(5)); // false
 
// Deleting - returns boolean indicating success
uniqueNumbers.delete(2);
console.log(uniqueNumbers.has(2)); // false
 
// Clearing all entries
uniqueNumbers.clear();
console.log(uniqueNumbers.size); // 0

Set Iteration

Sets maintain insertion order and provide iteration methods:

const colors = new Set(['red', 'green', 'blue', 'red']);
 
// for...of loop
for (const color of colors) {
    console.log(color); // red, green, blue
}
 
// forEach method - value and valueAgain are the same for Sets
colors.forEach((value, valueAgain) => {
    console.log(value); // Same as valueAgain
});
 
// Convert to array
const colorArray = [...colors]; // ['red', 'green', 'blue']
const colorArray2 = Array.from(colors); // ['red', 'green', 'blue']

Set from Arrays

// Create Set from array (automatically removes duplicates)
const numbers = [1, 2, 3, 2, 1, 4, 5, 4];
const uniqueSet = new Set(numbers);
console.log([...uniqueSet]); // [1, 2, 3, 4, 5]
 
// Common pattern: array deduplication
const uniqueArray = [...new Set(numbers)];
 
// Create Set from string (unique characters)
const charSet = new Set('hello');
console.log([...charSet]); // ['h', 'e', 'l', 'o']

Real-World Use Cases

Removing Duplicates

// Simple array deduplication
const numbers = [1, 2, 3, 2, 1, 4, 5, 4];
const unique = [...new Set(numbers)]; // [1, 2, 3, 4, 5]
 
// Object deduplication by property
const users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
    { id: 1, name: 'Alice' }, // duplicate
    { id: 3, name: 'Charlie' }
];
 
// Deduplicate by id using Map
const uniqueUsers = [...new Map(users.map(u => [u.id, u])).values()];
// [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 3, name: 'Charlie' }]
 
// Deduplicate by multiple properties
const uniqueByMultiple = [...new Map(
    users.map(u => [`${u.id}-${u.name}`, u])
).values()];

Caching with Map

Maps are excellent for implementing caches due to their O(1) access time and ordered structure:

class MemoCache {
    constructor(maxSize = 100) {
        this.cache = new Map();
        this.maxSize = maxSize;
    }
    
    get(key) {
        if (!this.cache.has(key)) return undefined;
        
        // Move to end (most recently used) - LRU behavior
        const value = this.cache.get(key);
        this.cache.delete(key);
        this.cache.set(key, value);
        return value;
    }
    
    set(key, value) {
        if (this.cache.has(key)) {
            this.cache.delete(key);
        } else if (this.cache.size >= this.maxSize) {
            // Delete oldest entry (first in Map iteration order)
            const oldestKey = this.cache.keys().next().value;
            this.cache.delete(oldestKey);
        }
        
        this.cache.set(key, value);
    }
    
    has(key) {
        return this.cache.has(key);
    }
    
    clear() {
        this.cache.clear();
    }
    
    get size() {
        return this.cache.size;
    }
}
 
// Usage
const cache = new MemoCache(50);
cache.set('user:1', { name: 'Alice' });
cache.set('user:2', { name: 'Bob' });
console.log(cache.get('user:1')); // { name: 'Alice' }

Counting Occurrences

Maps excel at counting operations:

function countOccurrences(items) {
    const counts = new Map();
    
    for (const item of items) {
        counts.set(item, (counts.get(item) || 0) + 1);
    }
    
    return counts;
}
 
const words = ['hello', 'world', 'hello', 'javascript', 'world', 'hello'];
const wordCounts = countOccurrences(words);
// Map { 'hello' => 3, 'world' => 2, 'javascript' => 1 }
 
// Get sorted results
const sorted = [...wordCounts.entries()]
    .sort((a, b) => b[1] - a[1]);
// [['hello', 3], ['world', 2], ['javascript', 1]]
 
// Character frequency analysis
function charFrequency(str) {
    const freq = new Map();
    for (const char of str.toLowerCase()) {
        if (char.match(/[a-z]/)) {
            freq.set(char, (freq.get(char) || 0) + 1);
        }
    }
    return freq;
}

Set Operations

Sets provide natural implementations of mathematical set operations:

function union(setA, setB) {
    return new Set([...setA, ...setB]);
}
 
function intersection(setA, setB) {
    return new Set([...setA].filter(x => setB.has(x)));
}
 
function difference(setA, setB) {
    return new Set([...setA].filter(x => !setB.has(x)));
}
 
function symmetricDifference(setA, setB) {
    return new Set([
        ...[...setA].filter(x => !setB.has(x)),
        ...[...setB].filter(x => !setA.has(x))
    ]);
}
 
function isSuperset(set, subset) {
    for (const elem of subset) {
        if (!set.has(elem)) return false;
    }
    return true;
}
 
// Usage
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);
 
console.log([...union(setA, setB)]);        // [1, 2, 3, 4, 5, 6]
console.log([...intersection(setA, setB)]); // [3, 4]
console.log([...difference(setA, setB)]);   // [1, 2]
console.log([...symmetricDifference(setA, setB)]); // [1, 2, 5, 6]
console.log(isSuperset(setA, new Set([1, 2]))); // true

Tag Management

class TagManager {
    constructor() {
        this.tags = new Map(); // tag -> Set of item IDs
        this.items = new Map(); // item ID -> Set of tags
    }
    
    addItem(itemId, tags) {
        this.items.set(itemId, new Set(tags));
        
        for (const tag of tags) {
            if (!this.tags.has(tag)) {
                this.tags.set(tag, new Set());
            }
            this.tags.get(tag).add(itemId);
        }
    }
    
    removeItem(itemId) {
        const tags = this.items.get(itemId);
        if (!tags) return;
        
        for (const tag of tags) {
            const tagItems = this.tags.get(tag);
            if (tagItems) {
                tagItems.delete(itemId);
                if (tagItems.size === 0) {
                    this.tags.delete(tag);
                }
            }
        }
        
        this.items.delete(itemId);
    }
    
    findByTag(tag) {
        return [...(this.tags.get(tag) || [])];
    }
    
    findByMultipleTags(tags) {
        const sets = tags.map(tag => this.tags.get(tag) || new Set());
        return [...intersection(...sets)];
    }
}
 
// Usage
const tagManager = new TagManager();
tagManager.addItem('post:1', ['javascript', 'tutorial', 'beginner']);
tagManager.addItem('post:2', ['javascript', 'advanced', 'performance']);
tagManager.addItem('post:3', ['python', 'tutorial', 'beginner']);
 
console.log(tagManager.findByTag('javascript')); // ['post:1', 'post:2']
console.log(tagManager.findByTag('tutorial'));   // ['post:1', 'post:3']

WeakMap and WeakSet

WeakMap

WeakMap keys must be objects and are held weakly, allowing garbage collection when no other references exist. This makes WeakMaps ideal for storing metadata associated with objects without preventing their cleanup:

const metadata = new WeakMap();
 
class User {
    constructor(name) {
        this.name = name;
        metadata.set(this, { 
            createdAt: Date.now(), 
            loginCount: 0,
            lastLogin: null 
        });
    }
    
    login() {
        const meta = metadata.get(this);
        meta.loginCount++;
        meta.lastLogin = Date.now();
    }
    
    getCreatedAt() {
        return metadata.get(this).createdAt;
    }
    
    getLoginCount() {
        return metadata.get(this).loginCount;
    }
}
 
// When User object is garbage collected, metadata is also removed
let user = new User('Alice');
user.login();
user.login();
console.log(user.getLoginCount()); // 2
 
user = null; // metadata entry is now eligible for garbage collection

WeakMaps cannot be iterated, which is by design. You cannot list all keys or values, which ensures the weak reference semantics are maintained.

WeakSet

WeakSet stores objects weakly, useful for tracking object instances without preventing garbage collection:

const visitedNodes = new WeakSet();
const processingNodes = new WeakSet();
 
function processNode(node) {
    if (visitedNodes.has(node)) return; // Already processed
    if (processingNodes.has(node)) return; // Currently being processed
    
    processingNodes.add(node);
    
    // Process node...
    for (const child of node.children) {
        processNode(child);
    }
    
    processingNodes.delete(node);
    visitedNodes.add(node);
}
 
// Nodes are automatically cleaned up when no longer referenced

WeakSets are also useful for implementing private properties and preventing memory leaks in event handling systems.

Best Practices for Production

  1. Use Map for key-value storage: Prefer Map over plain objects when keys aren't strings or when you need ordered data with explicit size tracking.

  2. Use Set for uniqueness: Use Set instead of array filtering for deduplication, especially with large datasets.

  3. Use WeakMap for private data: Store private metadata associated with objects without preventing garbage collection.

  4. Convert efficiently: Use Array.from() or spread operator for conversions between Maps, Sets, and arrays. Avoid unnecessary intermediate arrays.

  5. Consider performance: Map and Set have O(1) average case for get, set, has, and delete operations, making them ideal for frequent lookups.

  6. Use appropriate data structures: Choose Map when you need flexible keys and ordered data. Choose Set when you need uniqueness guarantees.

Performance Comparison

OperationMapObjectSetArray
Add/InsertO(1)O(1)O(1)O(1) amortized
Get/AccessO(1)O(1)O(1)O(n)
DeleteO(1)O(1)O(1)O(n)
Has/IncludesO(1)O(1)O(1)O(n)
SizeO(1)O(n)O(1)O(1)
IterationO(n)O(n)O(n)O(n)

Maps and Sets are significantly faster than arrays for lookups and deletions, especially with large datasets. Objects have similar performance to Maps for string keys but lack the flexibility of non-string keys and ordered iteration.

Community Resources and Further Learning

The technology landscape evolves rapidly, making continuous learning essential for maintaining expertise. Building a systematic approach to staying current with developments in your technology stack ensures you can leverage new features and avoid deprecated patterns.

Curated Learning Pathways

Rather than consuming content randomly, create structured learning pathways aligned with your current projects and career goals. Start with official documentation and specification documents, which provide the most accurate and comprehensive information. Follow this with hands-on tutorials and workshops that reinforce concepts through practical application.

Technical blogs from framework maintainers and core team members often provide deeper insights into design decisions and upcoming features. Subscribe to the official blogs of your primary frameworks and libraries to stay ahead of breaking changes and deprecation timelines.

Contributing to Open Source

Contributing to open-source projects in your technology stack provides unparalleled learning opportunities. Start with documentation improvements and bug reports, then progress to fixing small issues tagged as "good first issue" in your favorite projects. This direct engagement with maintainers and the codebase accelerates your understanding far beyond what passive learning can achieve.

# Setting up for contribution
git clone https://github.com/project/repository.git
cd repository
git checkout -b fix/issue-description
 
# Run the project's contribution setup
npm run setup:dev
npm run test  # Ensure tests pass before making changes
 
# Make your changes, then run the full test suite
npm run test:full
npm run lint
npm run build
 
# Submit your contribution
git add -A
git commit -m "fix: description of the fix
 
Closes #1234"
git push origin fix/issue-description

Building a Technical Knowledge Base

Maintain a personal knowledge base that captures insights, solutions, and patterns you discover during your work. Tools like Obsidian, Notion, or even a simple Markdown repository can serve as an external memory that grows more valuable over time.

Organize your notes by topic rather than chronologically, and include code examples, links to relevant documentation, and explanations of why certain approaches work better than others. When you encounter a particularly insightful article or conference talk, write a summary that captures the key takeaways and how they apply to your current projects.

Follow key conferences and their published talks to stay informed about emerging patterns and best practices. Many conferences publish recorded talks on YouTube within weeks of the event, making world-class technical content freely accessible.

Join relevant Discord servers, Slack communities, and forums where practitioners discuss real-world challenges and solutions. These communities provide early warning about emerging issues and access to collective wisdom that isn't available through formal documentation.

Mentorship and Knowledge Sharing

Teaching others is one of the most effective ways to deepen your own understanding. Consider writing technical blog posts, giving talks at local meetups, or mentoring junior developers. The process of explaining concepts to others forces you to organize your knowledge and identify gaps in your understanding.

Pair programming sessions with colleagues of different experience levels create mutual learning opportunities. Senior developers gain fresh perspectives on problems they've solved the same way for years, while junior developers benefit from exposure to production-grade thinking and decision-making processes.

Conclusion

Map and Set are essential JavaScript collections that solve real limitations of plain objects and arrays. Use Map for flexible key-value storage with any key type and Set for maintaining unique collections with efficient membership testing.

Key takeaways:

  1. Map preserves key types and maintains insertion order
  2. Set automatically prevents duplicate values
  3. WeakMap and WeakSet enable garbage-collectable associations
  4. These collections provide O(1) performance for core operations
  5. Use them when you need flexible keys, ordered data, or uniqueness guarantees

Master Map and Set to write more efficient and expressive JavaScript code that handles complex data processing requirements.