Map and Set in JavaScript

Map and Set in JavaScript are modern data structures introduced in ES6 to solve common problems developers face when working with objects and arrays.
While objects and arrays are powerful, they have limitations when it comes to handling key-value pairs and unique data. That’s where Map and Set come in—they provide cleaner, more efficient ways to manage data.
They're not replacements for objects and arrays, they're specialized tools that do certain jobs significantly better.
This article will show you exactly what those jobs are.
1. What Map Is ?
A Map is a collection of key-value pairs, just like an object.
The critical difference: Map accepts any value as a key. Numbers, objects, booleans, even other Maps. Objects only support strings and symbols as keys.
Creating and using a Map:
let userRoles = new Map();
userRoles.set("priya", "admin");
userRoles.set("rahul", "editor");
userRoles.set("ananya", "viewer");
console.log(userRoles.get("priya")); // "admin"
console.log(userRoles.has("rahul")); // true
console.log(userRoles.size); // 3
userRoles.delete("ananya");
console.log(userRoles.size); // 2
Notice the clean API : .set(), .get(), .has(), .delete(), .size. Everything is explicit and purpose-built, unlike objects where you use obj.key, delete obj.key, and Object.keys(obj).length.
2. What Set Is ?
A Set is a collection of unique values. Add the same value twice, it only appears once. That's the defining characteristic, and it's the whole reason Set exists.
let tags = new Set();
tags.add("javascript");
tags.add("frontend");
tags.add("javascript"); // duplicate — silently ignored
tags.add("es6");
console.log(tags.size); // 3 — not 4
console.log(tags); // Set { "javascript", "frontend", "es6" }
No error on duplicates. No fuss. They're just ignored.
Common Set operations:
let skills = new Set(["html", "css", "javascript", "css", "html"]);
console.log(skills.size); // 3 — duplicates removed automatically
console.log(skills.has("javascript")); // true
console.log(skills.has("python")); // false
skills.add("react");
skills.delete("html");
console.log([...skills]); // ["css", "javascript", "react"]
The most practical use : removing duplicates from an array:
let rawTags = ["javascript", "css", "javascript", "react", "css", "html"];
let uniqueTags = [...new Set(rawTags)];
console.log(uniqueTags);
// ["javascript", "css", "react", "html"]
One line. No loops, no filters, no manual checking. Create a Set (duplicates removed automatically), spread it back into an array.
Iterating a Set:
let categories = new Set(["books", "electronics", "clothing"]);
for (let category of categories) {
console.log(category);
}
// books
// electronics
// clothing
3. Map vs Object
Both store key-value pairs. Here's where they diverge in ways that actually matter:
Key types:
// Object — keys are always strings (or symbols)
let obj = {};
obj[1] = "one";
obj[true] = "yes";
console.log(Object.keys(obj)); // ["1", "true"] — converted to strings
// Map — keys keep their actual type
let map = new Map();
map.set(1, "one");
map.set(true, "yes");
console.log([...map.keys()]); // [1, true] — kept as number and boolean
Size:
let obj = { a: 1, b: 2, c: 3 };
let map = new Map([["a", 1], ["b", 2], ["c", 3]]);
// Object — no built-in size
console.log(Object.keys(obj).length); // 3 — manual calculation
// Map — built-in
console.log(map.size); // 3 — direct property
Iteration:
// Object — needs Object.entries(), not directly iterable
for (let [key, value] of Object.entries(obj)) {
console.log(key, value);
}
// Map — directly iterable
for (let [key, value] of map) {
console.log(key, value);
}
Prototype pollution risk:
Plain objects inherit from Object.prototype. This means they come with built-in properties like toString, hasOwnProperty, and constructor. These can cause subtle bugs if you're not careful with key names:
let permissions = {};
permissions["admin"] = true;
permissions["toString"] = true; // shadows Object.prototype.toString
// Now toString is broken on this object
console.log(permissions.toString()); // "true" — not the function anymore
Map has no such issue. It has no inherited prototype properties that could conflict with your data.
Side-by-side comparison:
| Object | Map | |
|---|---|---|
| Key types | Strings / Symbols only | Any value |
| Size | Manual (Object.keys().length) |
.size property |
| Iteration | Via Object.entries() |
Directly iterable |
| Insertion order | Not guaranteed (older JS) | Always preserved |
| Prototype risk | Yes | No |
| Best for | Structured records, config | Dynamic key-value storage |
4. Set vs Array
Both hold collections of values. The difference is purpose, arrays are ordered lists that allow duplicates; Sets are collections that guarantee uniqueness.
Duplicate behavior:
let arr = [1, 2, 2, 3, 3, 3];
console.log(arr.length); // 6 — keeps duplicates
let set = new Set([1, 2, 2, 3, 3, 3]);
console.log(set.size); // 3 — removes duplicates
Membership checking:
let arr = ["apple", "banana", "mango"];
let set = new Set(["apple", "banana", "mango"]);
// Array — scans the whole array O(n)
console.log(arr.includes("mango")); // true
// Set — direct lookup O(1)
console.log(set.has("mango")); // true
For large collections, this performance difference is significant. If you're frequently checking whether a value exists, Set is dramatically faster than an array.
What arrays can do that Set can't:
let arr = [1, 2, 3, 4, 5];
// Index access
console.log(arr[2]); // 3
// map, filter, reduce
let doubled = arr.map(x => x * 2);
// Duplicates (when you want them)
let repeated = [1, 1, 2, 2, 3];
Sets don't support index access set[0] gives you undefined. They also don't have map, filter, or reduce directly. For those operations, convert to an array first:
let set = new Set([1, 2, 3, 4, 5]);
let doubled = [...set].map(x => x * 2);
// [2, 4, 6, 8, 10]
Side-by-side comparison:
| Array | Set | |
|---|---|---|
| Duplicates | Allowed | Not allowed |
| Index access | Yes (arr[0]) |
No |
Has .map/.filter |
Yes | No (convert first) |
| Membership check | includes() — O(n) |
has() — O(1) |
| Order preserved | Yes | Yes |
| Best for | Ordered lists, transformations | Unique collections, lookup |
5. When to Use Map and Set
The clearest signal for reaching for Map or Set is when you find yourself working around a limitation of plain objects or arrays. Use Map when: You need non-string keys, especially when you want to associate data with DOM elements or objects:
let elementData = new Map();
let button = document.getElementById("submit");
let input = document.getElementById("email");
elementData.set(button, { clickCount: 0, lastClicked: null });
elementData.set(input, { errorCount: 0, lastError: null });
You need to know the size directly, or iterate keys and values frequently:
let config = new Map([
["theme", "dark"],
["language", "en"],
["timezone", "IST"]
]);
console.log(`${config.size} settings loaded`);
for (let [key, value] of config) {
applyConfig(key, value);
}
The data is highly dynamic, keys being added and removed frequently:
let activeSessions = new Map();
function login(userId, token) {
activeSessions.set(userId, { token, loginTime: Date.now() });
}
function logout(userId) {
activeSessions.delete(userId);
}
function isActive(userId) {
return activeSessions.has(userId);
}
Use Set when:
You're collecting values and don't want duplicates:
// Collecting unique error codes from multiple API calls
let errorCodes = new Set();
responses.forEach(response => {
if (response.error) errorCodes.add(response.error.code);
});
console.log([...errorCodes]); // only unique error codes
You need fast membership checking over a large list:
// Blocklist lookup — Set is much faster than array for this
let blockedUsers = new Set(["user_42", "user_99", "user_187"]);
function canPost(userId) {
return !blockedUsers.has(userId);
}
You want to find the union, intersection, or difference of two collections:
let setA = new Set([1, 2, 3, 4]);
let setB = new Set([3, 4, 5, 6]);
// Union — all values from both
let union = new Set([...setA, ...setB]);
// Set { 1, 2, 3, 4, 5, 6 }
// Intersection — only values in both
let intersection = new Set([...setA].filter(x => setB.has(x)));
// Set { 3, 4 }
// Difference — values in A but not B
let difference = new Set([...setA].filter(x => !setB.has(x)));
// Set { 1, 2 }
These patterns come up in real features, finding common friends, shared tags, users who belong to one group but not another.
Conclusion
Map and Set aren't replacements for objects and arrays. They're specialized tools that solve specific problems cleanly.
Reach for Map when your keys aren't strings, when you need reliable size and iteration, or when data is dynamic enough that an object's prototype quirks could be a problem.
Reach for Set when uniqueness matters, when you need fast membership checking, or when you want to perform set operations like union and intersection cleanly.



