How to Use Map and Set in JavaScript
JavaScript objects have traditionally served as the go-to data structure for storing key-value pairs, and arrays for ordered collections. But both have limitations. Objects can only use strings and symbols as keys, and arrays do not enforce uniqueness. When you need keys of any type, guaranteed insertion order, or a collection of unique values, plain objects and arrays fall short.
Map and Set are built-in data structures introduced in ES2015 that fill these gaps. A Map stores key-value pairs where keys can be of any type, including objects, functions, and even other Maps. A Set stores a collection of unique values, automatically preventing duplicates. Both maintain insertion order and provide efficient lookup, insertion, and deletion operations.
This guide covers everything you need to know about Map and Set: their methods, iteration patterns, conversion techniques, the new set-theory methods from ES2025, and the practical scenarios where they outperform plain objects and arrays.
Map: Keys of Any Type
A Map is a collection of key-value pairs where any value can be used as a key. This is the fundamental difference from plain objects, where keys are always converted to strings.
Creating a Map
// Empty Map
let map = new Map();
// Map from an iterable of [key, value] pairs
let userRoles = new Map([
["Alice", "admin"],
["Bob", "editor"],
["Charlie", "viewer"]
]);
console.log(userRoles);
// Map(3) { 'Alice' => 'admin', 'Bob' => 'editor', 'Charlie' => 'viewer' }
Any Type as a Key
This is where Maps truly shine. Unlike objects, Map keys are not converted to strings:
let map = new Map();
// String keys (just like objects)
map.set("name", "Alice");
// Number keys (objects would convert to string "1")
map.set(1, "one");
map.set(1.5, "one point five");
// Boolean keys
map.set(true, "yes");
map.set(false, "no");
// Object keys
let user = { id: 1, name: "Alice" };
map.set(user, { role: "admin", lastLogin: new Date() });
// Function keys
let handler = () => console.log("clicked");
map.set(handler, { eventType: "click", target: "button" });
// null and undefined as keys
map.set(null, "null value");
map.set(undefined, "undefined value");
console.log(map.get(1)); // "one"
console.log(map.get(user)); // { role: 'admin', lastLogin: 2026-03-03T20:56:05.020Z }
console.log(map.get(null)); // "null value"
console.log(map.get(true)); // "yes"
Object Keys: Reference Identity
When objects are used as Map keys, the Map uses reference identity for lookup. Two different objects with identical content are treated as different keys:
let map = new Map();
let obj1 = { name: "Alice" };
let obj2 = { name: "Alice" }; // Same content, different object
map.set(obj1, "value for obj1");
console.log(map.get(obj1)); // "value for obj1"
console.log(map.get(obj2)); // undefined (different reference!)
console.log(obj1 === obj2); // false
// Must use the exact same reference
let key = { id: 42 };
map.set(key, "found");
console.log(map.get(key)); // "found" (same reference)
This reference-based lookup is what enables Maps to use objects as keys, something plain objects cannot do because they would convert the object to the string "[object Object]":
// ❌ Plain objects fail with object keys
let plainObj = {};
let key1 = { id: 1 };
let key2 = { id: 2 };
plainObj[key1] = "value 1";
plainObj[key2] = "value 2";
console.log(Object.keys(plainObj)); // ["[object Object]"] (both keys collapsed!)
console.log(plainObj[key1]); // "value 2" (overwritten!)
// ✅ Map handles object keys correctly
let map = new Map();
map.set(key1, "value 1");
map.set(key2, "value 2");
console.log(map.get(key1)); // "value 1"
console.log(map.get(key2)); // "value 2" (separate entries)
console.log(map.size); // 2
How Map Compares Keys
Map uses an algorithm called SameValueZero to compare keys, which is essentially the same as strict equality (===) with one exception: NaN is considered equal to NaN.
let map = new Map();
// NaN as a key works correctly
map.set(NaN, "not a number");
console.log(map.get(NaN)); // "not a number"
console.log(NaN === NaN); // false (normal JS behavior)
// But Map treats NaN === NaN as true (by design)
// +0 and -0 are treated as the same key
map.set(0, "zero");
console.log(map.get(-0)); // "zero"
Map Methods: set, get, has, delete, clear, size
Map provides a clean, consistent API for all operations.
set(key, value)
Adds or updates a key-value pair. Returns the Map itself, enabling chaining:
let map = new Map();
map.set("name", "Alice");
map.set("age", 30);
map.set("city", "Rome");
// Chaining
map
.set("language", "JavaScript")
.set("level", "senior")
.set("active", true);
console.log(map.size); // 6
// Updating an existing key
map.set("age", 31); // Overwrites the previous value
console.log(map.get("age")); // 31
get(key)
Returns the value associated with the key, or undefined if the key does not exist:
let map = new Map([
["name", "Alice"],
["age", 30]
]);
console.log(map.get("name")); // "Alice"
console.log(map.get("age")); // 30
console.log(map.get("missing")); // undefined
has(key)
Returns true if the key exists, false otherwise:
let map = new Map([
["name", "Alice"],
["active", false]
]);
console.log(map.has("name")); // true
console.log(map.has("missing")); // false
// Correctly distinguishes "key exists with falsy value" from "key doesn't exist"
console.log(map.has("active")); // true
console.log(map.get("active")); // false (the value IS false, but the key exists)
This is cleaner than object property checking. With objects, checking if (obj.active) would give false for both "property is false" and "property does not exist."
delete(key)
Removes a key-value pair. Returns true if the key existed, false otherwise:
let map = new Map([
["a", 1],
["b", 2],
["c", 3]
]);
console.log(map.delete("b")); // true (existed and was removed)
console.log(map.delete("z")); // false (didn't exist)
console.log(map.size); // 2
console.log(map.has("b")); // false
clear()
Removes all key-value pairs:
let map = new Map([["a", 1], ["b", 2], ["c", 3]]);
console.log(map.size); // 3
map.clear();
console.log(map.size); // 0
size (Property)
Returns the number of key-value pairs. Unlike Object.keys(obj).length, this is a direct property with O(1) performance:
let map = new Map();
map.set("x", 1);
map.set("y", 2);
console.log(map.size); // 2
// Compare with objects (no direct size property)
let obj = { x: 1, y: 2 };
console.log(Object.keys(obj).length); // 2 (must compute each time)
Iterating Over Maps
Maps are iterable and maintain insertion order. Every iteration method visits entries in the order they were added.
keys(), values(), entries()
Each returns an iterable iterator:
let menu = new Map([
["coffee", 4.50],
["tea", 3.00],
["juice", 5.25]
]);
// Iterate over keys
for (let item of menu.keys()) {
console.log(item);
}
// coffee
// tea
// juice
// Iterate over values
for (let price of menu.values()) {
console.log(price);
}
// 4.5
// 3
// 5.25
// Iterate over [key, value] entries
for (let entry of menu.entries()) {
console.log(entry);
}
// ["coffee", 4.5]
// ["tea", 3]
// ["juice", 5.25]
Default Iterator Is entries()
When you use for...of on a Map directly, it uses entries() by default. Combined with destructuring, this produces very clean code:
let menu = new Map([
["coffee", 4.50],
["tea", 3.00],
["juice", 5.25]
]);
// Destructuring in for...of (the most common pattern)
for (let [item, price] of menu) {
console.log(`${item}: $${price.toFixed(2)}`);
}
// coffee: $4.50
// tea: $3.00
// juice: $5.25
forEach
Maps have their own forEach method. Note the callback argument order: value, key, map (value comes first, matching Array's forEach convention):
let menu = new Map([
["coffee", 4.50],
["tea", 3.00],
["juice", 5.25]
]);
menu.forEach((value, key) => {
console.log(`${key}: $${value.toFixed(2)}`);
});
// coffee: $4.50
// tea: $3.00
// juice: $5.25
Spread with Maps
Since Maps are iterable, the spread operator works:
let map = new Map([
["a", 1],
["b", 2],
["c", 3]
]);
// Spread into an array of entries
let entries = [...map];
console.log(entries); // [["a", 1], ["b", 2], ["c", 3]]
// Spread keys into an array
let keys = [...map.keys()];
console.log(keys); // ["a", "b", "c"]
// Spread values into an array
let values = [...map.values()];
console.log(values); // [1, 2, 3]
Map vs. Plain Objects: When to Use Which
Both Maps and plain objects store key-value pairs, but they have different strengths.
Feature Comparison
| Feature | Map | Plain Object {} |
|---|---|---|
| Key types | Any type (objects, functions, primitives) | Strings and Symbols only |
| Key order | Guaranteed insertion order | Insertion order (with caveats for integer keys) |
| Size | map.size (O(1)) | Object.keys(obj).length (O(n)) |
| Iteration | Direct: for...of, forEach, keys(), values(), entries() | Indirect: Object.keys(), Object.values(), Object.entries() |
| Performance (frequent add/delete) | Optimized for frequent changes | Not optimized |
| Default keys | None (clean slate) | Inherits from Object.prototype |
| JSON serialization | Not directly (need conversion) | Native support |
| Destructuring access | Not supported (map.name does not work) | Native (obj.name, const { name } = obj) |
When to Use Map
- Keys are not strings (objects, functions, DOM elements, numbers)
- You need to frequently add and delete entries
- You need to know the size efficiently
- Insertion order matters and must be reliable
- You need a clean key space (no inherited properties)
- Working with dynamic keys that are not known at code-writing time
// Map: tracking click counts for DOM elements
let clickCounts = new Map();
document.querySelectorAll("button").forEach(button => {
clickCounts.set(button, 0);
button.addEventListener("click", () => {
clickCounts.set(button, clickCounts.get(button) + 1);
console.log(`Clicked ${clickCounts.get(button)} times`);
});
});
When to Use Plain Objects
- Keys are strings and known at code-writing time
- You need JSON serialization (API communication)
- You need destructuring or property shorthand
- You are defining a record (fixed structure with known fields)
- Compatibility with existing APIs that expect objects
// Object: a user record with known fields
let user = {
name: "Alice",
age: 30,
role: "admin"
};
// Easy destructuring
let { name, age } = user;
// Direct JSON serialization
let json = JSON.stringify(user);
If your keys are strings and your data has a fixed, known shape, use a plain object. If your keys could be any type, or you are building a dynamic collection where entries are frequently added and removed, use a Map.
Converting Between Maps and Objects
Object.entries → Map
Object.entries() returns an array of [key, value] pairs, which is exactly the format the Map constructor accepts:
let prices = {
coffee: 4.50,
tea: 3.00,
juice: 5.25
};
let priceMap = new Map(Object.entries(prices));
console.log(priceMap.get("coffee")); // 4.5
console.log(priceMap.size); // 3
for (let [item, price] of priceMap) {
console.log(`${item}: $${price}`);
}
// coffee: $4.5
// tea: $3
// juice: $5.25
Map → Object.fromEntries
Object.fromEntries() takes an iterable of [key, value] pairs and creates a plain object. Since Map iterates as [key, value] pairs, conversion is direct:
let priceMap = new Map([
["coffee", 4.50],
["tea", 3.00],
["juice", 5.25]
]);
let priceObj = Object.fromEntries(priceMap);
console.log(priceObj);
// { coffee: 4.5, tea: 3, juice: 5.25 }
// Now you can use JSON.stringify
console.log(JSON.stringify(priceObj));
// '{"coffee":4.5,"tea":3,"juice":5.25}'
When converting a Map to an object, non-string keys are converted to strings. Object keys with non-string types lose their original type:
let map = new Map();
map.set(1, "one");
map.set(true, "yes");
map.set({ id: 1 }, "object");
let obj = Object.fromEntries(map);
console.log(obj);
// { '1': 'one', true: 'yes', '[object Object]': 'object' }
// All keys became strings!
Transforming Map Data
You can use the conversion pattern to apply object-style transformations to Map data:
let prices = new Map([
["coffee", 4.50],
["tea", 3.00],
["juice", 5.25]
]);
// Apply a 10% discount to all prices
let discounted = new Map(
[...prices].map(([item, price]) => [item, price * 0.9])
);
console.log(discounted.get("coffee")); // 4.05
// Filter entries
let affordable = new Map(
[...prices].filter(([item, price]) => price < 5)
);
console.log([...affordable]); // [["coffee", 4.5], ["tea", 3]]
Set: Unique Values Collection
A Set is a collection of unique values. Each value can appear only once. If you try to add a duplicate, it is silently ignored.
Creating a Set
// Empty Set
let emptySet = new Set();
// Set from an iterable
let numbers = new Set([1, 2, 3, 4, 5]);
console.log(numbers); // Set(5) { 1, 2, 3, 4, 5 }
// Duplicates are automatically removed
let withDupes = new Set([1, 2, 3, 2, 1, 4, 3, 5]);
console.log(withDupes); // Set(5) { 1, 2, 3, 4, 5 }
// From a string (each character becomes a unique value)
let chars = new Set("hello");
console.log(chars); // Set(4) { "h", "e", "l", "o" } (duplicate "l" removed)
Value Uniqueness
Like Map, Set uses SameValueZero comparison. This means:
let set = new Set();
// NaN is considered equal to NaN
set.add(NaN);
set.add(NaN);
console.log(set.size); // 1 (only one NaN)
// +0 and -0 are considered equal
set.add(0);
set.add(-0);
console.log(set.size); // 2 (NaN and 0)
// Objects are compared by reference
set.add({ name: "Alice" });
set.add({ name: "Alice" }); // Different object reference
console.log(set.size); // 4 (both objects are kept (different references))
// Same reference = same value
let obj = { id: 1 };
set.add(obj);
set.add(obj);
console.log(set.size); // 5 (only one copy of obj)
Set Methods: add, has, delete, clear, size
add(value)
Adds a value to the Set. Returns the Set itself, enabling chaining:
let colors = new Set();
colors.add("red");
colors.add("green");
colors.add("blue");
// Chaining
colors
.add("yellow")
.add("purple")
.add("red"); // Already exists (silently ignored)
console.log(colors.size); // 5 (not 6, "red" was not added again)
has(value)
Returns true if the value exists in the Set:
let colors = new Set(["red", "green", "blue"]);
console.log(colors.has("red")); // true
console.log(colors.has("yellow")); // false
has is significantly faster than Array.includes() for large collections. Set lookup is O(1) on average, while array search is O(n).
delete(value)
Removes a value. Returns true if the value existed, false otherwise:
let colors = new Set(["red", "green", "blue"]);
console.log(colors.delete("green")); // true
console.log(colors.delete("yellow")); // false (didn't exist)
console.log(colors.size); // 2
clear()
Removes all values:
let colors = new Set(["red", "green", "blue"]);
colors.clear();
console.log(colors.size); // 0
size (Property)
Returns the number of unique values:
let set = new Set([1, 2, 3, 2, 1]);
console.log(set.size); // 3
New Set Methods (ES2025)
ES2025 introduces set-theory operations that previously required manual implementation. These methods return new Sets without modifying the originals.
union(otherSet)
Returns a new Set containing all values from both Sets:
let frontend = new Set(["JavaScript", "TypeScript", "HTML", "CSS"]);
let backend = new Set(["JavaScript", "Python", "Go", "TypeScript"]);
let allLanguages = frontend.union(backend);
console.log(allLanguages);
// Set(6) { 'JavaScript', 'TypeScript', 'HTML', 'CSS', 'Python', 'Go' }
intersection(otherSet)
Returns a new Set containing only values present in both Sets:
let frontend = new Set(["JavaScript", "TypeScript", "HTML", "CSS"]);
let backend = new Set(["JavaScript", "Python", "Go", "TypeScript"]);
let shared = frontend.intersection(backend);
console.log(shared);
// Set(2) { "JavaScript", "TypeScript" }
difference(otherSet)
Returns a new Set containing values in the first Set but not in the second:
let frontend = new Set(["JavaScript", "TypeScript", "HTML", "CSS"]);
let backend = new Set(["JavaScript", "Python", "Go", "TypeScript"]);
let frontendOnly = frontend.difference(backend);
console.log(frontendOnly);
// Set(2) { "HTML", "CSS" }
let backendOnly = backend.difference(frontend);
console.log(backendOnly);
// Set(2) { "Python", "Go" }
symmetricDifference(otherSet)
Returns values that are in either Set but not in both:
let frontend = new Set(["JavaScript", "TypeScript", "HTML", "CSS"]);
let backend = new Set(["JavaScript", "Python", "Go", "TypeScript"]);
let exclusive = frontend.symmetricDifference(backend);
console.log(exclusive);
// Set(4) { "HTML", "CSS", "Python", "Go" }
isSubsetOf(otherSet)
Returns true if every value in this Set is also in the other Set:
let web = new Set(["HTML", "CSS", "JavaScript"]);
let allTech = new Set(["HTML", "CSS", "JavaScript", "Python", "Go"]);
console.log(web.isSubsetOf(allTech)); // true
console.log(allTech.isSubsetOf(web)); // false
isSupersetOf(otherSet)
Returns true if this Set contains every value from the other Set:
let allTech = new Set(["HTML", "CSS", "JavaScript", "Python", "Go"]);
let web = new Set(["HTML", "CSS", "JavaScript"]);
console.log(allTech.isSupersetOf(web)); // true
console.log(web.isSupersetOf(allTech)); // false
isDisjointFrom(otherSet)
Returns true if the two Sets have no values in common:
let fruits = new Set(["apple", "banana", "cherry"]);
let colors = new Set(["red", "green", "blue"]);
let mixed = new Set(["cherry", "red", "kiwi"]);
console.log(fruits.isDisjointFrom(colors)); // true (no overlap)
console.log(fruits.isDisjointFrom(mixed)); // false ("cherry" is shared)
Pre-ES2025 Equivalents
If these methods are not yet available in your environment, here are manual implementations:
// Union
function union(setA, setB) {
return new Set([...setA, ...setB]);
}
// Intersection
function intersection(setA, setB) {
return new Set([...setA].filter(x => setB.has(x)));
}
// Difference
function difference(setA, setB) {
return new Set([...setA].filter(x => !setB.has(x)));
}
// Symmetric difference
function symmetricDifference(setA, setB) {
return new Set([
...[...setA].filter(x => !setB.has(x)),
...[...setB].filter(x => !setA.has(x))
]);
}
Iterating Over Sets
Sets are iterable and maintain insertion order.
for...of
let fruits = new Set(["apple", "banana", "cherry"]);
for (let fruit of fruits) {
console.log(fruit);
}
// apple
// banana
// cherry
keys(), values(), entries()
Sets have keys(), values(), and entries() for consistency with Map, but since Sets have only values (no keys), keys() and values() return the same thing:
let set = new Set(["a", "b", "c"]);
// values(): the natural iteration
for (let val of set.values()) {
console.log(val);
}
// a, b, c
// keys(): identical to values() for compatibility with Map
for (let key of set.keys()) {
console.log(key);
}
// a, b, c
// entries(): returns [value, value] pairs (key === value)
for (let entry of set.entries()) {
console.log(entry);
}
// ["a", "a"]
// ["b", "b"]
// ["c", "c"]
The entries() method returning [value, value] may seem odd, but it ensures that Sets and Maps have a consistent API, allowing generic code to work with both.
forEach
let fruits = new Set(["apple", "banana", "cherry"]);
fruits.forEach((value, valueAgain, set) => {
console.log(value);
// valueAgain === value (for Map compatibility, both arguments are the same)
});
// apple
// banana
// cherry
Converting to Array
let set = new Set([3, 1, 4, 1, 5, 9, 2, 6]);
// Spread operator
let arr = [...set];
console.log(arr); // [3, 1, 4, 5, 9, 2, 6]
// Array.from
let arr2 = Array.from(set);
console.log(arr2); // [3, 1, 4, 5, 9, 2, 6]
Use Cases: Deduplication, Fast Lookups
Array Deduplication
The most common use case for Set. Convert an array to a Set (removing duplicates), then back to an array:
let numbers = [1, 2, 3, 2, 1, 4, 5, 3, 4];
let unique = [...new Set(numbers)];
console.log(unique); // [1, 2, 3, 4, 5]
This is dramatically simpler and more efficient than the manual filter + indexOf approach:
// ❌ Verbose and O(n²)
let unique2 = numbers.filter((val, i, arr) => arr.indexOf(val) === i);
// ✅ Clean and O(n)
let unique3 = [...new Set(numbers)];
Deduplicating Objects by Property
Sets use reference equality for objects, so you need a different approach for deduplicating by property value:
let users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 1, name: "Alice" }, // Duplicate by id
{ id: 3, name: "Charlie" },
{ id: 2, name: "Bob" } // Duplicate by id
];
// Deduplicate by id using Map (which keeps only the last value for each key)
let uniqueUsers = [...new Map(users.map(u => [u.id, u])).values()];
console.log(uniqueUsers);
// [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }, { id: 3, name: "Charlie" }]
Fast Membership Checking
When you need to check if values exist in a collection repeatedly, Set provides O(1) lookups compared to Array's O(n):
let allowedEmails = new Set([
"alice@example.com",
"bob@example.com",
"charlie@example.com",
// ... imagine thousands more
]);
function isAllowed(email) {
return allowedEmails.has(email); // O(1) lookup
}
console.log(isAllowed("alice@example.com")); // true
console.log(isAllowed("hacker@evil.com")); // false
// Compare with array: O(n) for each check
let allowedArray = ["alice@example.com", "bob@example.com", /* ... */];
function isAllowedSlow(email) {
return allowedArray.includes(email); // O(n) to check each element
}
Tracking Visited Items
function processItems(items) {
let processed = new Set();
for (let item of items) {
if (processed.has(item.id)) {
console.log(`Skipping duplicate: ${item.id}`);
continue;
}
processed.add(item.id);
console.log(`Processing: ${item.id}`);
}
console.log(`Total unique items processed: ${processed.size}`);
}
processItems([
{ id: "a", data: 1 },
{ id: "b", data: 2 },
{ id: "a", data: 3 }, // Duplicate
{ id: "c", data: 4 },
{ id: "b", data: 5 } // Duplicate
]);
// Processing: a
// Processing: b
// Skipping duplicate: a
// Processing: c
// Skipping duplicate: b
// Total unique items processed: 3
Toggling Selections
Sets are ideal for managing a list of selected items in a UI:
let selectedIds = new Set();
function toggleSelection(id) {
if (selectedIds.has(id)) {
selectedIds.delete(id);
console.log(`Deselected: ${id}`);
} else {
selectedIds.add(id);
console.log(`Selected: ${id}`);
}
console.log(`Current selection: [${[...selectedIds].join(", ")}]`);
}
toggleSelection(1); // Selected: 1, Current: [1]
toggleSelection(3); // Selected: 3, Current: [1, 3]
toggleSelection(1); // Deselected: 1, Current: [3]
toggleSelection(5); // Selected: 5, Current: [3, 5]
Counting Unique Values
let words = "the cat sat on the mat the cat".split(" ");
let uniqueWordCount = new Set(words).size;
console.log(uniqueWordCount); // 5 ("the", "cat", "sat", "on", "mat")
// Counting unique characters
let text = "mississippi";
let uniqueChars = new Set(text).size;
console.log(uniqueChars); // 4 ("m", "i", "s", "p")
Finding Common Elements Between Arrays
function findCommon(arr1, arr2) {
let set = new Set(arr1);
return arr2.filter(item => set.has(item));
}
let frontend = ["JavaScript", "TypeScript", "HTML", "CSS"];
let backend = ["JavaScript", "Python", "Go", "TypeScript"];
console.log(findCommon(frontend, backend));
// ["JavaScript", "TypeScript"]
Map as a Cache
Maps are excellent for caching computed results:
let cache = new Map();
function expensiveComputation(input) {
if (cache.has(input)) {
console.log(`Cache hit for: ${input}`);
return cache.get(input);
}
console.log(`Computing for: ${input}`);
let result = input * input; // Simulate expensive work
cache.set(input, result);
return result;
}
console.log(expensiveComputation(5)); // Computing for: 5, returns 25
console.log(expensiveComputation(5)); // Cache hit for: 5, returns 25
console.log(expensiveComputation(10)); // Computing for: 10, returns 100
console.log(cache.size); // 2
Map for Counting Occurrences
function countOccurrences(arr) {
let counts = new Map();
for (let item of arr) {
counts.set(item, (counts.get(item) || 0) + 1);
}
return counts;
}
let words = "the cat sat on the mat the cat".split(" ");
let wordCounts = countOccurrences(words);
for (let [word, count] of wordCounts) {
console.log(`"${word}" appears ${count} time(s)`);
}
// "the" appears 3 time(s)
// "cat" appears 2 time(s)
// "sat" appears 1 time(s)
// "on" appears 1 time(s)
// "mat" appears 1 time(s)
// Most frequent word
let mostFrequent = [...wordCounts].sort((a, b) => b[1] - a[1])[0];
console.log(`Most frequent: "${mostFrequent[0]}" (${mostFrequent[1]} times)`);
// Most frequent: "the" (3 times)
Map for Bidirectional Lookup
function createBiMap(entries) {
let forward = new Map(entries);
let reverse = new Map(entries.map(([k, v]) => [v, k]));
return {
get(key) { return forward.get(key); },
getByValue(value) { return reverse.get(value); },
get size() { return forward.size; }
};
}
let countryCodes = createBiMap([
["US", "United States"],
["GB", "United Kingdom"],
["IT", "Italy"],
["FR", "France"]
]);
console.log(countryCodes.get("IT")); // "Italy"
console.log(countryCodes.getByValue("France")); // "FR"
Performance Comparison
| Operation | Array | Object | Map | Set |
|---|---|---|---|---|
| Add element | push O(1) | obj[key]=val O(1) | set O(1) | add O(1) |
| Delete by value | filter O(n) | delete O(1) | delete O(1) | delete O(1) |
| Has value | includes O(n) | in / hasOwn O(1) | has O(1) | has O(1) |
| Get size | length O(1) | Object.keys().length O(n) | size O(1) | size O(1) |
| Iteration | Fast | Moderate | Fast | Fast |
| Key types | Numeric index | String/Symbol | Any | N/A |
| Uniqueness | No | By key | By key | Yes |
Summary
Map:
- Stores key-value pairs where keys can be of any type, including objects, functions, and primitives.
- Maintains insertion order and provides O(1)
get,set,has, anddeleteoperations. - Use
map.sizefor instant count (unlikeObject.keys(obj).length). - Iterate with
for...of(destructuring:for (let [key, value] of map)),keys(),values(),entries(), orforEach. - Convert object to Map with
new Map(Object.entries(obj)). Convert Map to object withObject.fromEntries(map). - Choose Map over objects when keys are not strings, when entries are frequently added/removed, or when you need reliable size and order.
Set:
- Stores unique values of any type. Duplicates are automatically ignored.
- Maintains insertion order and provides O(1)
add,has, anddeleteoperations. - The most common use case is array deduplication:
[...new Set(array)]. - ES2025 adds set-theory methods:
union,intersection,difference,symmetricDifference,isSubsetOf,isSupersetOf,isDisjointFrom. - Ideal for fast membership testing, tracking visited items, managing selections, and any scenario where uniqueness matters.
Both Map and Set use SameValueZero comparison (like === but NaN === NaN), are iterable, maintain insertion order, and use reference identity for object keys/values.