DEV Community

Harman Panwar
Harman Panwar

Posted on

Map and Set in JavaScript

JavaScript Map and Set: The Complete Guide

Introduction

As JavaScript developers, we often reach for the familiar tools: objects to store key-value pairs, and arrays to hold collections of items. But what happens when these traditional data structures aren't quite enough? What if you need guaranteed key ordering, or a collection where every item must be unique?

That's where Map and Set come in—two powerful ES6 data structures that solve problems objects and arrays can't handle elegantly. In this guide, we'll explore what makes these data structures special, when to use them, and how they can transform the way you write JavaScript code.


Table of Contents

  1. Understanding the Problem with Traditional Data Structures
  2. What is Map?
  3. What is Set?
  4. Map vs Object: Key Differences
  5. Set vs Array: Understanding Uniqueness
  6. When to Use Map and Set
  7. Practical Examples and Interview Scenarios
  8. Best Practices and Suggestions

Understanding the Problem with Traditional Data Structures

Before we dive into Map and Set, let's understand why JavaScript needed these new data structures. The problems they solve aren't obvious until you encounter them yourself.

Problems with Traditional Objects

Objects in JavaScript are powerful but come with quirks that can cause headaches:

Problem 1: Unexpected Key Order
----------------------------------

const obj = {};
obj.d = "fourth";
obj.a = "first";
obj.10 = "tenth";  // SyntaxError: Invalid key
obj[1] = "second"; // Gets converted to string "1"

console.log(Object.keys(obj)); // ["1", "d", "a"]
Enter fullscreen mode Exit fullscreen mode

The key ordering in objects is inconsistent and unexpected. Numeric keys are always sorted first, which can lead to bugs you won't notice until production.

Problem 2: Prototype Pollution
----------------------------------

const user = { name: "John" };
console.log(user.toString); // ƒ toString() { [native code] }

user.toString = "malicious";
console.log(user.toString); // "malicious"
Enter fullscreen mode Exit fullscreen mode

Every object inherits properties from its prototype. This means you might accidentally overwrite or check inherited properties you didn't intend to use.

Problem 3: Key Type Limitations
----------------------------------

const obj = {};
obj[{}] = "value";        // Uses "[object Object]" as key
obj[{name: "obj"}] = "v"; // Overwrites! Same "[object Object]" key

console.log(obj); // { '[object Object]': 'v' }
Enter fullscreen mode Exit fullscreen mode

You can't use objects as keys in another object—they get converted to strings, causing collisions you might not expect.

Problems with Traditional Arrays

Arrays are versatile but weren't designed for every use case:

Problem 1: No Built-in Uniqueness Enforcement
----------------------------------

const ids = [1, 2, 3, 2, 1, 4];
// You need to manually check for duplicates
const uniqueIds = [...new Set(ids)]; // [1, 2, 3, 4]

// But what if you accidentally add a duplicate?
if (!ids.includes(5)) {
    ids.push(5); // Someone else might forget this check
}
Enter fullscreen mode Exit fullscreen mode

Every time you need uniqueness, you must implement it yourself—or use a workaround like converting to a Set and back.

Problem 2: Slow Lookups
----------------------------------

const users = [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
    { id: 3, name: "Charlie" }
];

// Finding a user by ID requires iteration
const user = users.find(u => u.id === 2); // O(n) operation

// No efficient way to do direct lookups
Enter fullscreen mode Exit fullscreen mode

Arrays have no efficient built-in way to look up items by a specific property. Every search requires iteration through the entire array.


What is Map?

Map is a collection of key-value pairs that remembers the insertion order of its keys. It provides efficient operations for storing and retrieving data with any type of key.

Creating and Using Map

// Creating a new Map
const userRoles = new Map();

// Adding entries with set()
userRoles.set("john", "admin");
userRoles.set("jane", "editor");
userRoles.set("bob", "viewer");

// Reading values with get()
console.log(userRoles.get("john")); // "admin"

// Checking existence with has()
console.log(userRoles.has("jane")); // true

// Getting the size
console.log(userRoles.size); // 3

// Deleting entries with delete()
userRoles.delete("bob");
console.log(userRoles.size); // 2

// Clearing all entries
userRoles.clear();
console.log(userRoles.size); // 0
Enter fullscreen mode Exit fullscreen mode

Key Features of Map

1. Any Type of Key

Unlike objects, Maps allow keys of any type—strings, numbers, objects, even functions:

const map = new Map();

// String keys
map.set("name", "Alice");

// Number keys
map.set(1, "first");

// Object keys
const userKey = { id: 123 };
map.set(userKey, { name: "Bob", email: "bob@example.com" });

// Function keys
const keyFunction = () => "key";
map.set(keyFunction, "function key");

console.log(map.size); // 4
console.log(map.get(userKey)); // { name: "Bob", email: "bob@example.com" }
console.log(map.get(keyFunction)); // "function key"
Enter fullscreen mode Exit fullscreen mode

2. Guaranteed Key Order

Maps remember the order in which entries were inserted. This is particularly useful when order matters:

const settings = new Map();
settings.set("theme", "dark");
settings.set("language", "en");
settings.set("notifications", true);

// Iterating maintains insertion order
for (const [key, value] of settings) {
    console.log(`${key}: ${value}`);
}
// Output:
// theme: dark
// language: en
// notifications: true
Enter fullscreen mode Exit fullscreen mode

3. Size Property

Maps have a built-in size property that's always accurate—no need to calculate it manually:

const cache = new Map();
cache.set("a", 1);
cache.set("b", 2);

console.log(cache.size); // 2 (direct access, O(1))

// Compare to objects:
// Object.keys(cache).length; // O(n) operation every time
Enter fullscreen mode Exit fullscreen mode

Iterating Over Maps

Maps provide multiple ways to iterate:

const fruits = new Map([
    ["apple", 5],
    ["banana", 3],
    ["orange", 2]
]);

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

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

// Getting all keys, values, or entries
console.log([...fruits.keys()]);   // ["apple", "banana", "orange"]
console.log([...fruits.values()]); // [5, 3, 2]
console.log([...fruits.entries()]); // [["apple", 5], ["banana", 3], ["orange", 2]]
Enter fullscreen mode Exit fullscreen mode

Converting Map to Object and Back

// Map to Object
const map = new Map([
    ["a", 1],
    ["b", 2]
]);
const obj = Object.fromEntries(map);
// { a: 1, b: 2 }

// Object to Map
const anotherObj = { c: 3, d: 4 };
const anotherMap = new Map(Object.entries(anotherObj));
// Map { "c" => 3, "d" => 4 }
Enter fullscreen mode Exit fullscreen mode

What is Set?

Set is a collection of unique values—no duplicates allowed. It's perfect when you need to ensure no repeated values in a collection.

Creating and Using Set

// Creating a new Set
const uniqueIds = new Set();

// Adding values
uniqueIds.add(1);
uniqueIds.add(2);
uniqueIds.add(3);
uniqueIds.add(2); // Ignored—2 already exists

console.log(uniqueIds.size); // 3 (not 4!)
console.log(uniqueIds.has(2)); // true
console.log(uniqueIds.has(5)); // false

// Deleting values
uniqueIds.delete(2);
console.log(uniqueIds.size); // 2

// Clearing all values
uniqueIds.clear();
Enter fullscreen mode Exit fullscreen mode

Understanding the Uniqueness Property

This is the killer feature of Set. Every value can only appear once:

const emailSet = new Set();

// Users register their emails
emailSet.add("alice@example.com");
emailSet.add("bob@example.com");
emailSet.add("charlie@example.com");

// Alice tries to register again
emailSet.add("alice@example.com"); // Silently ignored

console.log(emailSet.size); // Still 3, not 4!
Enter fullscreen mode Exit fullscreen mode

The Uniqueness Check Works with Objects Too:

const user1 = { name: "Alice", age: 30 };
const user2 = { name: "Alice", age: 30 }; // Same structure, different object

const userSet = new Set();
userSet.add(user1);
userSet.add(user2); // Added—different memory reference!

console.log(userSet.size); // 2
Enter fullscreen mode Exit fullscreen mode

Note: For objects, uniqueness is based on reference equality, not the content. Two objects with identical properties are still considered different in a Set.

Converting Between Set and Array

// Array to Set (removes duplicates automatically)
const nums = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4];
const uniqueNums = new Set(nums);
console.log([...uniqueNums]); // [1, 2, 3, 4]

// Set to Array
const fruits = new Set(["apple", "banana", "orange"]);
const fruitsArray = [...fruits];
// OR
const fruitsArray2 = Array.from(fruits);
Enter fullscreen mode Exit fullscreen mode

Practical Example: Removing Duplicates

// Before Set: Manual deduplication
function removeDuplicatesManual(arr) {
    const result = [];
    for (const item of arr) {
        if (!result.includes(item)) {
            result.push(item);
        }
    }
    return result;
}

// With Set: Simple and efficient
function removeDuplicatesSet(arr) {
    return [...new Set(arr)];
}

// Test with large array
const data = [1, 2, 3, 2, 1, 4, 5, 4, 3, 2, 1];
console.log(removeDuplicatesSet(data)); // [1, 2, 3, 4, 5]
Enter fullscreen mode Exit fullscreen mode

Map vs Object: Key Differences

Understanding when to use Map instead of Object is crucial for writing clean, efficient JavaScript.

Side-by-Side Comparison

Feature Map Object
Key Types Any value Strings or Symbols
Key Order Insertion order Partially ordered
Size map.size Object.keys(obj).length
Iteration Directly iterable Object.keys() needed
Performance Optimized for frequent additions General purpose
Prototype No prototype pollution Inherits from prototype

Detailed Comparison

1. Key Types

// Map: Any type of key
const map = new Map();
const keyObj = { id: 1 };
map.set(keyObj, "value");
console.log(map.get(keyObj)); // "value"

// Object: Only string or symbol keys
const obj = {};
obj[{ id: 1 }] = "value"; // Gets stringified
console.log(obj["[object Object]"]); // "value"
Enter fullscreen mode Exit fullscreen mode

2. Iteration

// Map: Direct iteration
const settings = new Map([
    ["theme", "dark"],
    ["lang", "en"]
]);

for (const [key, value] of settings) {
    console.log(`${key}: ${value}`);
}

// Object: Requires Object.keys()
const settingsObj = { theme: "dark", lang: "en" };
for (const key of Object.keys(settingsObj)) {
    console.log(`${key}: ${settingsObj[key]}`);
}
Enter fullscreen mode Exit fullscreen mode

3. Size Property

const map = new Map();
map.set("a", 1);
map.set("b", 2);

const obj = { a: 1, b: 2 };

console.log(map.size); // 2 (direct, O(1))
console.log(Object.keys(obj).length); // 2 (calculated each time, O(n))
Enter fullscreen mode Exit fullscreen mode

When to Choose Map Over Object

Use Map when:

  • You need to use non-string keys (numbers, objects, functions)
  • You need guaranteed iteration order
  • You're frequently adding or removing key-value pairs
  • You need to know the collection size frequently
  • You're building a key-value cache or lookup table

Use Object when:

  • You need to store structured data with known properties
  • You're working with JSON (JSON only supports objects)
  • You need prototype methods or inheritance
  • You want to use object literal syntax { a: 1 }

Real-World Example: User Role Management

// Using Map (Recommended)
const userRoles = new Map([
    ["user_123", { name: "Alice", role: "admin" }],
    ["user_456", { name: "Bob", role: "editor" }],
    ["user_789", { name: "Charlie", role: "viewer" }]
]);

// Efficient lookup
const user = userRoles.get("user_123");
console.log(user.role); // "admin"

// Easy iteration for displaying all users
for (const [id, data] of userRoles) {
    console.log(`${data.name} (${id}): ${data.role}`);
}

// Using Object (Problematic)
const userRolesObj = {
    "user_123": { name: "Alice", role: "admin" },
    "user_456": { name: "Bob", role: "editor" }
};
// Object lacks built-in size and direct iteration
Enter fullscreen mode Exit fullscreen mode

Set vs Array: Understanding Uniqueness

The choice between Set and Array isn't always obvious. Let's explore when each shines.

Side-by-Side Comparison

Feature Set Array
Duplicates Automatically prevented Allowed
Lookup set.has(item) - O(1) array.includes() - O(n)
Insertion set.add(item) - O(1)* array.push() - O(1)
Deletion set.delete(item) - O(1)* Find + splice - O(n)
Ordering Insertion order Insertion order

*Average case complexity

Detailed Comparison

1. Lookup Performance

const largeArray = Array.from({ length: 10000 }, (_, i) => i);
const largeSet = new Set(largeArray);

// Array lookup: O(n) - gets slower as array grows
console.time("Array includes");
largeArray.includes(9999);
console.timeEnd("Array includes"); // ~0.5ms

// Set lookup: O(1) - constant time regardless of size
console.time("Set has");
largeSet.has(9999);
console.timeEnd("Set has"); // ~0.001ms
Enter fullscreen mode Exit fullscreen mode

2. Automatic Uniqueness

const tags = new Set();

// Blog post tags
tags.add("javascript");
tags.add("web");
tags.add("tutorial");
tags.add("javascript"); // Silently ignored
tags.add("web");        // Silently ignored

console.log([...tags]); // ["javascript", "web", "tutorial"]

// Same with Array: you'd need manual checking
const tagsArray = [];
function addTag(arr, tag) {
    if (!arr.includes(tag)) {
        arr.push(tag);
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Deletion

const colors = new Set(["red", "green", "blue", "yellow"]);

// Set: Direct deletion by value, O(1) average
colors.delete("green");
console.log([...colors]); // ["red", "blue", "yellow"]

// Array: Find index, then splice, O(n)
const colorsArray = ["red", "green", "blue", "yellow"];
const index = colorsArray.indexOf("green");
if (index > -1) {
    colorsArray.splice(index, 1);
}
Enter fullscreen mode Exit fullscreen mode

When to Choose Set Over Array

Use Set when:

  • You need to ensure all values are unique
  • You're frequently checking for value existence
  • You're working with mathematical set operations (union, intersection, difference)
  • You need efficient deletion by value
  • You're storing items where duplicates should be prevented

Use Array when:

  • You need to preserve insertion order and allow duplicates
  • You need to store items in a specific sequence
  • You're using array methods like map(), filter(), reduce()
  • You're working with APIs that expect arrays
  • You need random access by index

Practical Example: Tracking Visited Pages

// Using Array: Multiple entries for the same page
const visitedPages = [];
visitedPages.push("/home");
visitedPages.push("/about");
visitedPages.push("/home"); // Duplicate!
visitedPages.push("/contact");
console.log(visitedPages); // ["/home", "/about", "/home", "/contact"]

// Unique visits needed
const uniqueVisits = new Set();
uniqueVisits.add("/home");
uniqueVisits.add("/about");
uniqueVisits.add("/home"); // Ignored
uniqueVisits.add("/contact");
console.log([...uniqueVisits]); // ["/home", "/about", "/contact"]

// Quick check if user visited a page
function hasVisited(url) {
    return uniqueVisits.has(url);
}
console.log(hasVisited("/home")); // true
Enter fullscreen mode Exit fullscreen mode

When to Use Map and Set

Now that we understand Map and Set individually and compared them with their traditional counterparts, let's consolidate when to use each.

Decision Tree

Do you need to store key-value pairs?
│
├── Yes: Are keys always strings?
│       ├── Yes: Consider Object if structure is fixed
│       └── No: Use Map
│
└── No: Do you need to store a collection of unique items?
        ├── Yes: Use Set
        └── No: Use Array
Enter fullscreen mode Exit fullscreen mode

Real-World Use Cases

1. Caching Function Results (Map)

// Memoization cache with Map
const cache = new Map();

function expensiveCalculation(n) {
    // Check cache first
    if (cache.has(n)) {
        console.log("Using cached result");
        return cache.get(n);
    }

    // Perform calculation
    const result = n * n * n; // Expensive operation

    // Cache the result
    cache.set(n, result);

    return result;
}

console.log(expensiveCalculation(5)); // 125 (calculated)
console.log(expensiveCalculation(5)); // 125 (cached)
Enter fullscreen mode Exit fullscreen mode

2. Tracking User Permissions (Set)

function UserPermissions() {
    this.permissions = new Set();
}

UserPermissions.prototype.grant = function(permission) {
    this.permissions.add(permission);
};

UserPermissions.prototype.revoke = function(permission) {
    this.permissions.delete(permission);
};

UserPermissions.prototype.has = function(permission) {
    return this.permissions.has(permission);
};

UserPermissions.prototype.list = function() {
    return [...this.permissions];
};

const user = new UserPermissions();
user.grant("read");
user.grant("write");
user.grant("read"); // Ignored (already has permission)

console.log(user.list()); // ["read", "write"]
console.log(user.has("admin")); // false
Enter fullscreen mode Exit fullscreen mode

3. Building an Adjacency List (Map + Set)

// Graph representation using Map and Set
const graph = new Map();

// Adding vertices with empty neighbor sets
function addVertex(name) {
    if (!graph.has(name)) {
        graph.set(name, new Set());
    }
}

// Adding edges (undirected graph)
function addEdge(from, to) {
    addVertex(from);
    addVertex(to);
    graph.get(from).add(to);
    graph.get(to).add(from);
}

// Finding neighbors
function getNeighbors(vertex) {
    return graph.has(vertex) ? [...graph.get(vertex)] : [];
}

addEdge("A", "B");
addEdge("A", "C");
addEdge("B", "C");
addEdge("C", "D");

console.log(getNeighbors("A")); // ["B", "C"]
console.log(getNeighbors("C")); // ["A", "B", "D"]
Enter fullscreen mode Exit fullscreen mode

Practical Examples and Interview Scenarios

Scenario 1: Finding First Non-Repeating Character

Problem: Find the first character in a string that doesn't repeat.

Solution using Set and Map:

function firstNonRepeating(str) {
    // Count occurrences using Map
    const charCount = new Map();

    for (const char of str) {
        charCount.set(char, (charCount.get(char) || 0) + 1);
    }

    // Find first character with count of 1
    for (const char of str) {
        if (charCount.get(char) === 1) {
            return char;
        }
    }

    return null;
}

console.log(firstNonRepeating("aabbc")); // "c"
console.log(firstNonRepeating("aabbcc")); // null
Enter fullscreen mode Exit fullscreen mode

Scenario 2: Two Sum Problem (Efficient Version)

Problem: Find two numbers that add up to a target.

Map-based solution:

function twoSum(nums, target) {
    const numMap = new Map();

    for (let i = 0; i < nums.length; i++) {
        const complement = target - nums[i];

        if (numMap.has(complement)) {
            return [numMap.get(complement), i];
        }

        numMap.set(nums[i], i);
    }

    return null;
}

console.log(twoSum([2, 7, 11, 15], 9)); // [0, 1]
console.log(twoSum([3, 2, 4], 6)); // [1, 2]
Enter fullscreen mode Exit fullscreen mode

Why Map is better than Array here:

  • Array: O(n) to find complement, O(n²) total
  • Map: O(1) to find complement, O(n) total

Scenario 3: Union and Intersection of Arrays

Using Set operations:

function union(arr1, arr2) {
    return [...new Set([...arr1, ...arr2])];
}

function intersection(arr1, arr2) {
    const set1 = new Set(arr1);
    return [...new Set(arr2.filter(item => set1.has(item)))];
}

function difference(arr1, arr2) {
    const set2 = new Set(arr2);
    return arr1.filter(item => !set2.has(item));
}

const setA = [1, 2, 3, 4];
const setB = [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]
Enter fullscreen mode Exit fullscreen mode

Scenario 4: LRU Cache Implementation

Using Map for automatic ordering:

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

    get(key) {
        if (!this.cache.has(key)) {
            return null;
        }

        // 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 exists, remove first
        if (this.cache.has(key)) {
            this.cache.delete(key);
        }

        // Add new entry
        this.cache.set(key, value);

        // Remove oldest if over capacity
        if (this.cache.size > this.maxSize) {
            const firstKey = this.cache.keys().next().value;
            this.cache.delete(firstKey);
        }
    }
}

const cache = new LRUCache(3);
cache.put("a", 1);
cache.put("b", 2);
cache.put("c", 3);
console.log(cache.get("a")); // 1 (moved to most recent)
cache.put("d", 4);           // Removes "b" (least recent)
console.log(cache.get("b")); // null
Enter fullscreen mode Exit fullscreen mode

Best Practices and Suggestions

1. Use Map Instead of Object for Dynamic Keys

// ❌ Avoid: Using object for dynamic key-value pairs
const data = {};
for (const item of items) {
    data[item.id] = item.value;
}

// ✅ Better: Use Map
const data = new Map();
for (const item of items) {
    data.set(item.id, item.value);
}
Enter fullscreen mode Exit fullscreen mode

2. Use Set for Unique Collections

// ❌ Avoid: Manual uniqueness checking
const uniqueItems = [];
for (const item of items) {
    if (!uniqueItems.includes(item)) {
        uniqueItems.push(item);
    }
}

// ✅ Better: Use Set
const uniqueItems = [...new Set(items)];
Enter fullscreen mode Exit fullscreen mode

3. Use WeakMap for Object Keys with Garbage Collection

// ✅ Use WeakMap when keys should be garbage collected
const privateData = new WeakMap();

function User(name) {
    privateData.set(this, { name, createdAt: Date.now() });
}

User.prototype.getName = function() {
    return privateData.get(this).name;
};

const user = new User("Alice");
// When user is no longer referenced, privateData entry is garbage collected
Enter fullscreen mode Exit fullscreen mode

4. Choose the Right Data Structure from the Start

Type of Data          →  Best Choice
─────────────────────────────────────────
Key-value storage     →  Map
Unique items only     →  Set
Frequent lookups     →  Map or Set
Fixed schema data     →  Object
Ordered list         →  Array
Enter fullscreen mode Exit fullscreen mode

5. Convert Wisely Between Data Structures

// Object ↔ Map
const objToMap = new Map(Object.entries(obj));
const mapToObj = Object.fromEntries(map);

// Array ↔ Set
const arrayToSet = new Set(array);
const setToArray = [...set];
Enter fullscreen mode Exit fullscreen mode

6. Be Mindful of Browser Support

Map and Set are ES6 features. For older browser support, consider using polyfills or transpilers like Babel.

// For IE11 support, you might need:
if (typeof Map === "undefined") {
    // Include core-js or babel-polyfill
}
Enter fullscreen mode Exit fullscreen mode

Summary

Map and Set are powerful additions to JavaScript that solve real problems with traditional objects and arrays.

Key Takeaways:

  1. Map is perfect for key-value storage when you need non-string keys, guaranteed ordering, or efficient size checking.

  2. Set is ideal for collections that must contain unique values, providing O(1) lookup and automatic deduplication.

  3. Performance matters: Use Map and Set when you need frequent lookups or uniqueness enforcement.

  4. Readability matters: Choosing the right data structure makes your code self-documenting and easier to maintain.

  5. They complement arrays and objects: Use all four data structures strategically based on your specific needs.

Remember: There's no one-size-fits-all solution. Objects are still great for structured data, arrays for ordered collections—but when you need Map's ordering or Set's uniqueness, these ES6 structures will make your code cleaner and more efficient.


Further Reading


This guide should give you a solid foundation for using Map and Set effectively in your JavaScript projects. Happy coding!

Top comments (0)