Skip to main content

Command Palette

Search for a command to run...

Map and Set in JavaScript

JavaScript already had objects and arrays. So why did ES6 introduce Map and Set? Because objects and arrays have real limitations — and Map and Set were built to solve them specifically.

Updated
•11 min read•View as Markdown
C
Software developer passionate about building scalable web applications with React and backend technologies. I enjoy solving problems, building projects, and sharing my learning with the community.

Table of Contents

  1. What Is a Map?

  2. What Is a Set?

  3. Map vs Object — The Real Differences

  4. Set vs Array — The Real Differences

  5. When to Use Map and Set


1. What Is a Map?

A Map is a collection of key-value pairs — just like a plain object. The critical difference is what you can use as a key.

In a plain object, keys can only be strings or symbols. If you use anything else — a number, a function, another object — JavaScript silently converts it to a string first. That silent conversion is where bugs are born.

// Plain object — keys get silently converted to strings
const obj = {};
obj[1]         = "number key";
obj[true]      = "boolean key";
obj[{ id: 1 }] = "object key";

console.log(Object.keys(obj));
// ["1", "true", "[object Object]"]
// ↑ Every key got stringified — your actual values are gone

A Map doesn't do that. It remembers what you gave it — a number key stays a number, an object key stays that exact object reference. Keys in a Map are compared by strict equality (===), not by their string representation.

const map = new Map();

map.set(1, "number key");
map.set(true, "boolean key");
map.set({ id: 1 }, "object key");

console.log(map.size); // 3
// All three keys preserved exactly as given

Creating and using a Map

// Create empty and add entries
const userRoles = new Map();

userRoles.set("priya", "admin");
userRoles.set("arjun", "editor");
userRoles.set("meera", "viewer");

// Read a value
console.log(userRoles.get("priya")); // "admin"
console.log(userRoles.get("ravi"));  // undefined

// Check if a key exists
console.log(userRoles.has("arjun")); // true
console.log(userRoles.has("ravi"));  // false

// Remove an entry
userRoles.delete("meera");

// How many entries?
console.log(userRoles.size); // 2

// Clear everything
userRoles.clear();

Creating a Map from existing data

You can initialise a Map directly from an array of [key, value] pairs:

const prices = new Map([
  ["apple",  1.5],
  ["banana", 0.8],
  ["mango",  2.0]
]);

console.log(prices.get("mango")); // 2.0

This pattern is clean and commonly used when you have data from an API or a database that you want to work with efficiently.

Iterating over a Map

Map preserves the insertion order of its entries — something objects only guarantee for string keys in modern engines, and even then with caveats. With Map, the order is always exactly as you inserted.

const capitals = new Map([
  ["India",  "New Delhi"],
  ["France", "Paris"],
  ["Japan",  "Tokyo"]
]);

// Iterate over key-value pairs
for (const [country, capital] of capitals) {
  console.log(`\({country} → \){capital}`);
}
// India  → New Delhi
// France → Paris
// Japan  → Tokyo

// Just keys
for (const country of capitals.keys()) {
  console.log(country);
}

// Just values
for (const capital of capitals.values()) {
  console.log(capital);
}

// Convert to array of pairs
const entries = [...capitals.entries()];
// [["India", "New Delhi"], ["France", "Paris"], ["Japan", "Tokyo"]]

2. What Is a Set?

A Set is a collection of unique values. You can put anything into it — numbers, strings, objects — but it will never store the same value twice. If you try to add a duplicate, it's silently ignored.

This is the problem with plain arrays: they have no built-in concept of uniqueness. If you push the same value ten times, you get ten entries. Removing duplicates from an array the traditional way is clunky — filter with indexOf, or use a manual object as a lookup. Set makes uniqueness the default.

// Array — duplicates stack up with no warning
const arr = [1, 2, 2, 3, 3, 3];
console.log(arr.length); // 6 — all six entries exist

// Set — duplicates are silently ignored
const set = new Set([1, 2, 2, 3, 3, 3]);
console.log(set.size); // 3 — only unique values: {1, 2, 3}

Creating and using a Set

const tags = new Set();

tags.add("javascript");
tags.add("nodejs");
tags.add("webdev");
tags.add("javascript"); // duplicate — silently ignored

console.log(tags.size);               // 3
console.log(tags.has("nodejs"));      // true
console.log(tags.has("python"));      // false

tags.delete("webdev");
console.log(tags.size);              // 2

The most common use — removing duplicates from an array

This is a one-liner that every JavaScript developer should know:

const withDuplicates = [1, 2, 2, 3, 4, 4, 4, 5];

// Spread the Set back into an array
const unique = [...new Set(withDuplicates)];

console.log(unique); // [1, 2, 3, 4, 5]

What's happening: new Set(withDuplicates) creates a Set which drops duplicates. [...set] spreads it back into a plain array. Two steps, one line.

It works equally well with strings:

const words = ["apple", "banana", "apple", "cherry", "banana"];
const uniqueWords = [...new Set(words)];

console.log(uniqueWords); // ["apple", "banana", "cherry"]

Iterating over a Set

Set also preserves insertion order:

const permissions = new Set(["read", "write", "execute"]);

for (const permission of permissions) {
  console.log(permission);
}
// read
// write
// execute

// Convert to array when you need array methods
const permArray = [...permissions];
const upper = permArray.map(p => p.toUpperCase());
// ["READ", "WRITE", "EXECUTE"]

3. Map vs Object — The Real Differences

On the surface, a Map and a plain object both store key-value pairs. But they behave very differently in practice, and choosing the wrong one creates real problems.

Key types

// Object — only string/symbol keys (everything else gets coerced)
const obj = {};
obj[1]         = "value";
console.log(Object.keys(obj)); // ["1"] ← number became string

// Map — any value can be a key
const map = new Map();
const keyObj = { id: 42 };
const keyFn  = () => {};

map.set(keyObj, "object as key");
map.set(keyFn, "function as key");
map.set(1, "number as key");     // stays a number
map.set(NaN, "NaN as key");      // even NaN works

console.log(map.get(keyObj)); // "object as key"
console.log(map.get(1));      // "number as key"

Size

const obj = { a: 1, b: 2, c: 3 };
const map = new Map([["a", 1], ["b", 2], ["c", 3]]);

// Object — no built-in size, have to compute it
console.log(Object.keys(obj).length); // 3 — extra step

// Map — size is a built-in property
console.log(map.size); // 3 — direct

Inherited keys problem

This one is subtle but important. Plain objects inherit properties from Object.prototype. If you're using an object as a data store and checking whether a key exists, you can accidentally get a hit on an inherited property.

const obj = {};

// These keys don't exist on obj — but they come from Object.prototype
console.log("toString"    in obj); // true  ← false positive
console.log("constructor" in obj); // true  ← false positive
console.log("hasOwnProperty" in obj); // true  ← false positive

// Map starts completely empty — no inherited keys
const map = new Map();
console.log(map.has("toString"));    // false ✓
console.log(map.has("constructor")); // false ✓

If you use the in operator on objects to check for keys, you're vulnerable to this. Map doesn't have this problem.

Iteration order

// Object — ordering of integer keys can be surprising
const obj = {};
obj["b"] = 2;
obj["a"] = 1;
obj[2]   = "two";
obj[1]   = "one";

console.log(Object.keys(obj)); // ["1", "2", "b", "a"]
// Integer keys sort numerically first, then string keys in insertion order

// Map — always exactly insertion order, no exceptions
const map = new Map();
map.set("b", 2);
map.set("a", 1);
map.set(2, "two");
map.set(1, "one");

console.log([...map.keys()]); // ["b", "a", 2, 1]
// Exactly as inserted, regardless of key type

Side-by-side summary


4. Set vs Array — The Real Differences

Arrays and Sets both hold collections of values, but they have fundamentally different strengths.

Uniqueness

// Array — no uniqueness, duplicates welcome
const arr = [1, 2, 3];
arr.push(2); // silently added
arr.push(3); // silently added
console.log(arr); // [1, 2, 3, 2, 3]

// Set — uniqueness is the rule, duplicates silently ignored
const set = new Set([1, 2, 3]);
set.add(2); // ignored
set.add(3); // ignored
console.log([...set]); // [1, 2, 3]

Checking membership

This is a big one for performance. Checking whether a value exists in an array requires scanning every element until a match is found — that's O(n) time. Set lookups are O(1) — constant time regardless of how large the Set is.

const arr = [1, 2, 3, 4, 5];
const set = new Set([1, 2, 3, 4, 5]);

// Array — scans from the beginning until it finds the value
arr.includes(5);   // has to check 1, 2, 3, 4, then finds 5
arr.indexOf(5);    // same scan

// Set — goes directly to the answer
set.has(5);        // O(1) — instant, no scanning

For small collections this doesn't matter. For thousands of values, or when you're checking membership frequently, the difference is significant.

Removing a value

const arr = [1, 2, 3, 4];

// Array — find the index first, then splice it out
const index = arr.indexOf(3);
if (index !== -1) arr.splice(index, 1);
console.log(arr); // [1, 2, 4]

// Set — just delete
const set = new Set([1, 2, 3, 4]);
set.delete(3);
console.log([...set]); // [1, 2, 4]

Side-by-side summary


5. When to Use Map and Set

Use Map when...

You need non-string keys. If your keys are numbers, objects, or anything other than strings, a plain object will corrupt them. Map is the only reliable choice.

// DOM node as a key — object can't do this reliably
const elementMetadata = new Map();
const button = document.querySelector("#submit");

elementMetadata.set(button, { clickCount: 0, lastClicked: null });
elementMetadata.get(button).clickCount++;

You're building a dynamic key-value store where keys are added and removed frequently. Map's delete, has, and size make this far cleaner than managing it on a plain object.

// Session store — users log in and out
const sessions = new Map();

sessions.set("token_abc123", { userId: 1, expires: Date.now() + 3600000 });
sessions.set("token_xyz456", { userId: 2, expires: Date.now() + 3600000 });

// User logs out
sessions.delete("token_abc123");
console.log(sessions.size); // 1

You need guaranteed insertion-order iteration. When the order matters and you have mixed key types, Map is the only choice that behaves predictably.

You want a clean key space. No risk of toString, constructor, or hasOwnProperty showing up as false positives.


Use Set when...

You need a collection of unique values. This is the primary use case. Tags, permissions, visited URLs, selected items in a UI — anything where duplicates are invalid by definition.

// Track which pages a user has visited
const visited = new Set();

visited.add("/home");
visited.add("/about");
visited.add("/home");   // already visited — ignored
visited.add("/contact");

console.log(visited.size);         // 3
console.log(visited.has("/about")); // true

You need fast membership checks on a large collection. If you have thousands of items and you're frequently asking "is this value in the collection?", replace the array with a Set.

// Blocked email addresses — checked on every signup
const blocklist = new Set(["spam@domain.com", "fake@domain.com" /* ... thousands more */]);

function isBlocked(email) {
  return blocklist.has(email); // O(1) — doesn't slow down with more entries
}

You need to remove duplicates. The [...new Set(arr)] pattern is the standard, efficient way to deduplicate any array.

You're computing set operations — union, intersection, or difference between two collections:

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

// Union — all values from both
const union = new Set([...setA, ...setB]);
console.log([...union]); // [1, 2, 3, 4, 5, 6]

// Intersection — values in both
const intersection = new Set([...setA].filter(v => setB.has(v)));
console.log([...intersection]); // [3, 4]

// Difference — values in A but not in B
const difference = new Set([...setA].filter(v => !setB.has(v)));
console.log([...difference]); // [1, 2]

Quick Recap

// ── MAP ────────────────────────────────────

const map = new Map();
map.set("key", "value");    // add
map.get("key");              // read → "value"
map.has("key");              // check → true
map.delete("key");           // remove
map.size;                    // count
map.clear();                 // remove all

// Any key type
map.set(42, "number key");
map.set({}, "object key");

// Iterate
for (const [key, value] of map) { }


// ── SET ─────────────────────────────────────

const set = new Set();
set.add("value");            // add (duplicate ignored)
set.has("value");            // check → true
set.delete("value");         // remove
set.size;                    // count
set.clear();                 // remove all

// Deduplication
const unique = [...new Set([1, 2, 2, 3, 3])]; // [1, 2, 3]

// Iterate
for (const value of set) { }


// ── CHOOSE BY SITUATION ──────────────────────────────────────

// Need key-value pairs with any key type? → Map
// Need ordered unique values?              → Set
// Removing duplicates from an array?       → Set
// Static config, JSON serialisation?       → Plain object
// Ordered list with duplicates allowed?    → Array

Map and Set are not replacements for objects and arrays — they're additions for specific situations where objects and arrays fall short. Once you know what problems they were designed to solve, you'll recognise those situations immediately: mixed key types, uniqueness requirements, fast membership checks, clean dynamic stores. The right tool becomes obvious.

Next up → Promises in JavaScript: Solving Callback Hell with Chains.


Found this useful? Drop a reaction and share it with someone learning ES6. Got a question? Leave a comment — I read every one.

More from this blog

C

Chetan Chauhan | Tech Blog | chetan71

45 posts