Munsif.
AboutExperienceProjectsAchievementsBlogsContact
HomeAboutExperienceProjectsAchievementsBlogsContact
Munsif.

Frontend Developer crafting scalable web applications with modern technologies and clean code practices.

Quick Links

  • About
  • Experience
  • Projects
  • Achievements

Connect

© 2026 Shaik Munsif. All rights reserved.

Built with Next.js & Tailwind

0%
Welcome back!Continue where you left off
Back to Blogs
JavaScript

JavaScript Map & Set Mastery: Complete Guide to Modern Data Structures

Master Map, Set, WeakMap, and WeakSet. Learn when to use Map vs Object, Set operations like union and intersection, real-world patterns like LRU caches, and performance comparisons.

Feb 12, 202620 min read
JavaScriptData StructuresMapSetInterview Prep
JavaScript MasteryPart 9 of 11
  • 1. JavaScript Type Coercion Mastery: A Senior Engineer's Guide to the Chaos
  • 2. JavaScript Runtime Deep Dive: Event Loop, Call Stack & Async Execution
  • 3. Mastering JavaScript Callbacks: From Sync to Async & The Event Loop
  • 4. JavaScript Closures Deep Dive: Mastering Lexical Scope & Memory
  • 5. JavaScript Object Prototyping Mastery: A Visual Guide to Inheritance
  • 6. Demystifying the JavaScript 'this' Keyword: A Visual Guide
  • 7. JavaScript Promises & Promise Chaining Mastery: From Callbacks to Async Flow
  • 8. JavaScript Array Methods Mastery: Complete Guide from map() to reduce()
  • 9. JavaScript Map & Set Mastery: Complete Guide to Modern Data Structures
  • 10. JavaScript String Methods Mastery: Complete Guide from slice() to replaceAll()
  • 11. JavaScript Regular Expressions Mastery: Real-World Patterns & Validation

Introduction

JavaScript objects and arrays are the most common data structures, but they have limitations. Objects can only use strings and symbols as keys. Arrays don't enforce uniqueness. When you need key-value pairs with any type of key, or collections of unique values, you need Map and Set.

Introduced in ES2015, Map and Set are purpose-built data structures that solve specific problems better than objects and arrays. They're widely used in caching, deduplication, graph algorithms, DOM tracking, and more.

This guide covers everything you need to know—from basic operations to advanced patterns, WeakMap/WeakSet, real-world use cases, and interview questions.


Map vs Object vs Set vs Array — At a Glance

Common Methods: Map vs Set

Both Map and Set share a similar API surface. Here's how they compare:

OperationMapSetNotes
Add entrymap.set(key, value)set.add(value)Both are chainable and return the collection itself
Check existencemap.has(key)set.has(value)Both return boolean, both O(1)
Remove entrymap.delete(key)set.delete(value)Both return boolean (was found?)
Remove allmap.clear()set.clear()Both return undefined
Count entriesmap.sizeset.sizeBoth are properties, not methods
Get valuemap.get(key)❌ Not availableSets don't have key-value pairs
Iterate keysmap.keys()set.keys()Set keys and values are the same
Iterate valuesmap.values()set.values()Set values() is the default iterator
Iterate entriesmap.entries() → [key, value]set.entries() → [value, value]Set entries repeat the value as both key and value
forEachmap.forEach((val, key) => {})set.forEach((val, val2) => {})Set callback receives value twice (for API consistency)
for...offor (const [k, v] of map)for (const v of set)Map defaults to entries, Set defaults to values
Spread[...map] → [[k,v], ...][...set] → [v, ...]Easy conversion to arrays
Maintains order✅ Insertion order✅ Insertion orderBoth guarantee iteration in insertion order
Key equalitySameValueZeroSameValueZeroNaN === NaN is true in both
ℹ️ note

[!NOTE] Set's keys(), values(), and entries() exist for API consistency with Map. In practice, just use for...of or spread with Sets.


1. Map — Key-Value Pairs with Any Key Type

1.1 Creating a Map

javascript
// Empty Map
const map = new Map();

// From array of [key, value] pairs
const userRoles = new Map([
    ['alice', 'admin'],
    ['bob', 'editor'],
    ['charlie', 'viewer']
]);

console.log(userRoles);
// Map(3) { 'alice' => 'admin', 'bob' => 'editor', 'charlie' => 'viewer' }

1.2 Core Methods

MethodDescriptionReturns
set(key, value)Add or update a key-value pairThe Map itself (chainable)
get(key)Get value by keyValue or undefined
has(key)Check if key existsboolean
delete(key)Remove a key-value pairboolean (was it found?)
clear()Remove all entriesundefined
sizeNumber of entries (property, not method)number
javascript
const map = new Map();

// set() — returns the Map (chainable!)
map.set('name', 'Alice')
   .set('age', 30)
   .set('active', true);

// get()
console.log(map.get('name'));    // 'Alice'
console.log(map.get('missing')); // undefined

// has()
console.log(map.has('age'));     // true
console.log(map.has('email'));   // false

// size
console.log(map.size);           // 3

// delete()
map.delete('active');
console.log(map.size);           // 2

// clear()
map.clear();
console.log(map.size);           // 0

1.3 Any Type as Key

This is the killer feature of Map. Unlike objects, Map keys can be anything:

javascript
const map = new Map();

// Object as key
const user = { id: 1, name: 'Alice' };
map.set(user, 'premium');

// Function as key
const greet = () => 'Hello';
map.set(greet, 'greeting function');

// Number as key (not converted to string!)
map.set(1, 'one');
map.set('1', 'string one');  // Different key from above!

console.log(map.get(1));     // 'one'
console.log(map.get('1'));   // 'string one'
console.log(map.get(user));  // 'premium'

// DOM elements as keys
const button = document.querySelector('#submit');
map.set(button, { clicks: 0, lastClicked: null });
💜 important

[!IMPORTANT] Map uses the SameValueZero algorithm for key comparison. This means NaN === NaN is true in Maps (unlike regular ===), and -0 equals +0.

javascript
const map = new Map();
map.set(NaN, 'not a number');
console.log(map.get(NaN)); // 'not a number' ✅ (NaN === NaN in Maps)

1.4 Iterating Over a Map

Maps maintain insertion order and provide multiple iteration methods:

javascript
const fruits = new Map([
    ['apple', 1.5],
    ['banana', 0.75],
    ['cherry', 3.0]
]);

// forEach()
fruits.forEach((value, key) => {
    console.log(`${key}: $${value}`);
});

// for...of (default: entries)
for (const [key, value] of fruits) {
    console.log(`${key}: $${value}`);
}

// keys()
for (const key of fruits.keys()) {
    console.log(key); // 'apple', 'banana', 'cherry'
}

// values()
for (const value of fruits.values()) {
    console.log(value); // 1.5, 0.75, 3.0
}

// entries()
for (const [key, value] of fruits.entries()) {
    console.log(`${key} = ${value}`);
}

Converting Map to Array:

javascript
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);

const keys = [...map.keys()];       // ['a', 'b', 'c']
const values = [...map.values()];   // [1, 2, 3]
const entries = [...map.entries()]; // [['a', 1], ['b', 2], ['c', 3]]
const entries2 = [...map];          // Same as above

1.5 Map vs Object — When to Use Which

FeatureMapObject
Key typesAny (objects, functions, primitives)Strings & Symbols only
Key orderGuaranteed insertion orderMostly ordered (integers sorted first)
Sizemap.sizeObject.keys(obj).length
IterationDirectly iterable (for...of)Need Object.keys() / Object.entries()
PerformanceBetter for frequent add/deleteBetter for static data
Default keysNoneHas prototype keys (toString, etc.)
SerializationNot JSON-serializable by defaultJSON-serializable
DestructuringNot directly supportedSupported
💡 tip

[!TIP] Use Map when: keys are dynamic/unknown, keys are non-strings, you need frequent additions/deletions, you need to know the size, or order matters.

Use Object when: keys are known and static, you need JSON serialization, you need destructuring, or you're working with APIs that expect plain objects.


1.6 Map Serialization (JSON)

Maps aren't directly JSON-serializable, but conversion is easy:

javascript
const map = new Map([['name', 'Alice'], ['age', 30]]);

// Map → JSON
const json = JSON.stringify([...map]);
console.log(json); // '[["name","Alice"],["age",30]]'

// JSON → Map
const restored = new Map(JSON.parse(json));
console.log(restored.get('name')); // 'Alice'

// Map → Object (string keys only)
const obj = Object.fromEntries(map);
console.log(obj); // { name: 'Alice', age: 30 }

// Object → Map
const map2 = new Map(Object.entries(obj));

2. Set — Collections of Unique Values

2.1 Creating a Set

javascript
// Empty Set
const set = new Set();

// From array (duplicates removed automatically!)
const unique = new Set([1, 2, 3, 2, 1, 3]);
console.log(unique); // Set(3) { 1, 2, 3 }

// From string
const chars = new Set('hello');
console.log(chars); // Set(4) { 'h', 'e', 'l', 'o' }

2.2 Core Methods

MethodDescriptionReturns
add(value)Add a valueThe Set itself (chainable)
has(value)Check if value existsboolean
delete(value)Remove a valueboolean (was it found?)
clear()Remove all valuesundefined
sizeNumber of values (property)number
javascript
const colors = new Set();

// add() — chainable
colors.add('red')
      .add('green')
      .add('blue')
      .add('red');  // Ignored! Already exists

console.log(colors.size); // 3

// has() — O(1) lookup!
console.log(colors.has('red'));    // true
console.log(colors.has('yellow')); // false

// delete()
colors.delete('green');
console.log(colors.size); // 2

// clear()
colors.clear();
console.log(colors.size); // 0
ℹ️ note

[!NOTE] Set.has() is O(1) average time complexity, compared to Array.includes() which is O(n). This makes Sets significantly faster for large collections.


2.3 Iterating Over a Set

javascript
const fruits = new Set(['apple', 'banana', 'cherry']);

// for...of
for (const fruit of fruits) {
    console.log(fruit);
}

// forEach
fruits.forEach(value => console.log(value));

// Convert to Array
const arr = [...fruits];
// or
const arr2 = Array.from(fruits);

Sets maintain insertion order:

javascript
const set = new Set();
set.add('c').add('a').add('b');

console.log([...set]); // ['c', 'a', 'b'] (insertion order)

2.4 Array Deduplication with Set

The most common use case for Sets:

javascript
// Simple deduplication
const numbers = [1, 2, 3, 2, 1, 4, 5, 4];
const unique = [...new Set(numbers)];
console.log(unique); // [1, 2, 3, 4, 5]

// Deduplicate objects by property
const users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
    { id: 1, name: 'Alice' }  // Duplicate
];

const uniqueIds = [...new Set(users.map(u => u.id))];
console.log(uniqueIds); // [1, 2]

// Deduplicate and keep objects
const seen = new Set();
const uniqueUsers = users.filter(user => {
    if (seen.has(user.id)) return false;
    seen.add(user.id);
    return true;
});

2.5 Set Operations

Union (All elements from both Sets)

javascript
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);

const union = new Set([...setA, ...setB]);
console.log(union); // Set { 1, 2, 3, 4, 5, 6 }

// ES2025+ (built-in method)
const union2 = setA.union(setB);

Intersection (Common elements)

javascript
const intersection = new Set(
    [...setA].filter(x => setB.has(x))
);
console.log(intersection); // Set { 3, 4 }

// ES2025+
const intersection2 = setA.intersection(setB);

Difference (Elements in A but not in B)

javascript
const difference = new Set(
    [...setA].filter(x => !setB.has(x))
);
console.log(difference); // Set { 1, 2 }

// ES2025+
const difference2 = setA.difference(setB);

Symmetric Difference (Elements in either but not both)

javascript
const symDifference = new Set(
    [...setA].filter(x => !setB.has(x))
    .concat([...setB].filter(x => !setA.has(x)))
);
console.log(symDifference); // Set { 1, 2, 5, 6 }

// ES2025+
const symDifference2 = setA.symmetricDifference(setB);
💡 tip

[!TIP] The built-in Set methods (union(), intersection(), difference(), symmetricDifference()) are available in modern browsers and Node.js 22+. Use the manual implementations for older environments.


3. WeakMap — Maps with Garbage-Collectible Keys

3.1 What Makes WeakMap Special?

javascript
const weakMap = new WeakMap();

let user = { name: 'Alice' };
weakMap.set(user, 'some metadata');

console.log(weakMap.get(user)); // 'some metadata'

user = null; // The object can now be garbage collected!
// The WeakMap entry is automatically removed

WeakMap Restrictions:

FeatureMapWeakMap
Key typesAnyObjects only
Iterable✅ Yes❌ No
size✅ Available❌ Not available
clear()✅ Available❌ Not available
Garbage collectionKeys prevent GCKeys don't prevent GC

3.2 Use Cases

Private Data:

javascript
const privateData = new WeakMap();

class Person {
    constructor(name, age) {
        privateData.set(this, { name, age });
    }

    getName() {
        return privateData.get(this).name;
    }

    getAge() {
        return privateData.get(this).age;
    }
}

const alice = new Person('Alice', 30);
console.log(alice.getName()); // 'Alice'
console.log(alice.getAge());  // 30
// No way to access privateData from outside!

Caching/Memoization:

javascript
const cache = new WeakMap();

function expensiveOperation(obj) {
    if (cache.has(obj)) {
        console.log('Cache hit!');
        return cache.get(obj);
    }

    const result = /* ...expensive calculation... */ obj.value * 2;
    cache.set(obj, result);
    return result;
}

let data = { value: 42 };
expensiveOperation(data); // Computes
expensiveOperation(data); // Cache hit!

data = null; // Cache entry is automatically cleaned up!

DOM Metadata:

javascript
const elementData = new WeakMap();

function trackElement(element) {
    elementData.set(element, {
        clicks: 0,
        created: Date.now()
    });
}

function recordClick(element) {
    const data = elementData.get(element);
    if (data) data.clicks++;
}

// When the DOM element is removed, the metadata 
// is automatically garbage collected!

4. WeakSet — Sets with Garbage-Collectible Values

4.1 What Makes WeakSet Special?

javascript
const weakSet = new WeakSet();

let obj = { name: 'Alice' };
weakSet.add(obj);

console.log(weakSet.has(obj)); // true

obj = null; // Object can be garbage collected
// WeakSet entry is automatically removed

WeakSet has only 3 methods: add(), has(), delete()

4.2 Use Cases

Tracking Visited/Processed Objects:

javascript
const visited = new WeakSet();

function processNode(node) {
    if (visited.has(node)) {
        return; // Already processed — avoid infinite loops!
    }
    visited.add(node);

    // Process the node...
    console.log(`Processing: ${node.name}`);

    if (node.children) {
        node.children.forEach(child => processNode(child));
    }
}

Branding / Type Checking:

javascript
const validRequests = new WeakSet();

class Request {
    constructor(url) {
        this.url = url;
        validRequests.add(this);
    }
}

function processRequest(req) {
    if (!validRequests.has(req)) {
        throw new Error('Invalid request object!');
    }
    // Safe to process...
}

const req = new Request('/api/users');
processRequest(req);              // ✅ Works
processRequest({ url: '/hack' }); // ❌ Throws Error

5. Real-World Patterns

5.1 Frequency Counter (Map)

javascript
function wordFrequency(text) {
    const words = text.toLowerCase().match(/\b\w+\b/g) || [];
    const freq = new Map();

    for (const word of words) {
        freq.set(word, (freq.get(word) || 0) + 1);
    }

    // Sort by frequency (descending)
    return new Map(
        [...freq.entries()].sort((a, b) => b[1] - a[1])
    );
}

const result = wordFrequency('the cat sat on the mat the cat');
console.log(result);
// Map { 'the' => 3, 'cat' => 2, 'sat' => 1, 'on' => 1, 'mat' => 1 }

5.2 LRU Cache (Map)

Map's insertion order makes it perfect for Least Recently Used caches:

javascript
class LRUCache {
    constructor(capacity) {
        this.capacity = capacity;
        this.cache = new Map();
    }

    get(key) {
        if (!this.cache.has(key)) return -1;

        // Move to end (most recently used)
        const value = this.cache.get(key);
        this.cache.delete(key);
        this.cache.set(key, value);
        return value;
    }

    put(key, value) {
        if (this.cache.has(key)) {
            this.cache.delete(key);
        } else if (this.cache.size >= this.capacity) {
            // Delete oldest (first entry)
            const oldest = this.cache.keys().next().value;
            this.cache.delete(oldest);
        }
        this.cache.set(key, value);
    }
}

const cache = new LRUCache(3);
cache.put('a', 1);
cache.put('b', 2);
cache.put('c', 3);
cache.put('d', 4); // Evicts 'a'
console.log(cache.get('b')); // 2 (moves 'b' to end)

5.3 Bi-directional Lookup (Two Maps)

javascript
class BiMap {
    constructor() {
        this.forward = new Map();
        this.reverse = new Map();
    }

    set(key, value) {
        this.forward.set(key, value);
        this.reverse.set(value, key);
    }

    getByKey(key) {
        return this.forward.get(key);
    }

    getByValue(value) {
        return this.reverse.get(value);
    }
}

const countryCode = new BiMap();
countryCode.set('US', 'United States');
countryCode.set('IN', 'India');

console.log(countryCode.getByKey('IN'));           // 'India'
console.log(countryCode.getByValue('United States')); // 'US'

5.4 Unique Tag Collector (Set)

javascript
function collectTags(posts) {
    const allTags = new Set();

    posts.forEach(post => {
        post.tags.forEach(tag => allTags.add(tag));
    });

    return [...allTags].sort();
}

const posts = [
    { title: 'Post 1', tags: ['javascript', 'react'] },
    { title: 'Post 2', tags: ['javascript', 'node'] },
    { title: 'Post 3', tags: ['react', 'nextjs'] }
];

console.log(collectTags(posts));
// ['javascript', 'nextjs', 'node', 'react']

5.5 Permission System (Set)

javascript
class PermissionManager {
    constructor() {
        this.permissions = new Map(); // userId → Set of permissions
    }

    grant(userId, permission) {
        if (!this.permissions.has(userId)) {
            this.permissions.set(userId, new Set());
        }
        this.permissions.get(userId).add(permission);
    }

    revoke(userId, permission) {
        this.permissions.get(userId)?.delete(permission);
    }

    check(userId, permission) {
        return this.permissions.get(userId)?.has(permission) ?? false;
    }

    listPermissions(userId) {
        return [...(this.permissions.get(userId) || [])];
    }
}

const pm = new PermissionManager();
pm.grant('alice', 'read');
pm.grant('alice', 'write');
pm.grant('alice', 'read'); // Duplicate ignored

console.log(pm.check('alice', 'write')); // true
console.log(pm.listPermissions('alice')); // ['read', 'write']

6. Performance Comparison

6.1 Map vs Object Performance

OperationMapObject
InsertO(1)O(1)
LookupO(1)O(1)
DeleteO(1)O(1) (but delete can be slow)
SizeO(1) via .sizeO(n) via Object.keys().length
IterationFast (native iterator)Slower (create array first)

6.2 Set vs Array Performance

OperationSetArray
AddO(1)O(1) (push)
Has/IncludesO(1)O(n)
DeleteO(1)O(n) (splice)
SizeO(1)O(1)
⚠️ warning

[!WARNING] For large datasets with frequent lookups or membership checks, always prefer Set over Array. The difference between O(1) and O(n) becomes enormous at scale.

javascript
// Benchmark: Checking membership in 1,000,000 items
const arr = Array.from({ length: 1_000_000 }, (_, i) => i);
const set = new Set(arr);

console.time('Array.includes');
arr.includes(999_999);
console.timeEnd('Array.includes'); // ~2ms

console.time('Set.has');
set.has(999_999);
console.timeEnd('Set.has');        // ~0.001ms (1000x faster!)

7. Quick Interview Q&A

QuestionAnswer
Map vs Object?Map supports any key type, has guaranteed order, has .size, and is better for dynamic keys.
When to use Set?When you need unique values or fast O(1) membership checks.
WeakMap keys garbage collected?Yes. If no other reference exists to the key object, both the key and value are garbage collected.
Is Map iterable?Yes. Maps are directly iterable with for...of. Objects are not.
Set preserves order?Yes. Sets maintain insertion order.
Can Map have NaN as key?Yes. Map treats NaN === NaN as true (SameValueZero algorithm).
How to convert Map to JSON?JSON.stringify([...map]) or JSON.stringify(Object.fromEntries(map)) for string keys.
Set vs Array dedup?[...new Set(array)] is the cleanest deduplication pattern.

Conclusion

Map and Set are essential data structures that every JavaScript developer should master:

  1. Map: Use when you need key-value pairs with non-string keys, guaranteed order, or frequent additions/deletions
  2. Set: Use when you need unique values or fast membership checks
  3. WeakMap/WeakSet: Use when keys/values should be garbage-collectible (DOM tracking, caching, private data)
  4. Performance: Set's O(1) has() vs Array's O(n) includes() makes a massive difference at scale
  5. Patterns: LRU caches, permission systems, frequency counters, and deduplication are natural use cases

Don't default to objects and arrays for everything. Choose the right data structure, and your code will be cleaner, faster, and more expressive.

Happy coding! 🚀

🧠 Test Your Knowledge

Now that you've learned the concepts, let's see if you can apply them! Take this quick quiz to test your understanding.

PreviousJavaScript Array Methods Mastery: Complete Guide from map() to reduce()NextJavaScript String Methods Mastery: Complete Guide from slice() to replaceAll()

Written by

Shaik Munsif

Read more articles

Found this helpful? Share it with your network!

On this page

0/33
Question 1 of 10Easy
Score: 0/0

What types can be used as keys in a Map?