Two data structures that solve problems objects and arrays can't — and when you should reach for them.
For a while, I thought JavaScript only had two ways to store data: objects (key-value pairs) and arrays (ordered lists). They handle most situations, so I never questioned whether there was something better.
Then I ran into two problems that made me realize their limitations:
Problem 1: I needed an object where the keys were other objects, not just strings. Regular objects can't do that — every key gets converted to a string.
Problem 2: I needed an array with no duplicates. I kept adding values and then manually checking "wait, is this already in there?" before each insertion.
Both problems have clean solutions: Map and Set. They were introduced in ES6 (2015), and once you understand what they do, you'll wonder why you didn't learn them sooner.
Let me walk you through both.
What Is a Map?
A Map is a collection of key-value pairs — similar to an object — but with some important upgrades.
The biggest difference: Map keys can be anything. Objects, arrays, numbers, functions — literally any value can be a key. With regular objects, keys are always converted to strings.
Creating and Using a Map
const userRoles = new Map();
// Setting key-value pairs
userRoles.set("Pratham", "admin");
userRoles.set("Arjun", "editor");
userRoles.set("Priya", "viewer");
// Getting values
console.log(userRoles.get("Pratham")); // "admin"
console.log(userRoles.get("Arjun")); // "editor"
// Checking if a key exists
console.log(userRoles.has("Priya")); // true
console.log(userRoles.has("Rahul")); // false
// Size
console.log(userRoles.size); // 3
// Deleting
userRoles.delete("Arjun");
console.log(userRoles.size); // 2
You Can Also Initialize with Values
const config = new Map([
["theme", "dark"],
["language", "en"],
["fontSize", 16],
]);
console.log(config.get("theme")); // "dark"
Visual: Map Key-Value Storage
┌──────────────────────────────────────┐
│ Map │
├──────────────┬───────────────────────┤
│ KEY │ VALUE │
├──────────────┼───────────────────────┤
│ "Pratham" │ "admin" │
│ "Arjun" │ "editor" │
│ "Priya" │ "viewer" │
├──────────────┴───────────────────────┤
│ .get(key) → value │
│ .set(key, value) → adds/updates │
│ .has(key) → true/false │
│ .delete(key) → removes pair │
│ .size → number of pairs │
└──────────────────────────────────────┘
The Superpower: Any Value as a Key
This is where Map pulls ahead of regular objects:
const scores = new Map();
// Object as key — impossible with regular objects
const student1 = { name: "Pratham" };
const student2 = { name: "Arjun" };
scores.set(student1, 95);
scores.set(student2, 88);
console.log(scores.get(student1)); // 95
console.log(scores.get(student2)); // 88
With a regular object, both keys would become the string "[object Object]" and overwrite each other. Map keeps them as separate, distinct keys.
// Number as key
const cache = new Map();
cache.set(404, "Not Found");
cache.set(200, "OK");
console.log(cache.get(404)); // "Not Found"
// Function as key
const fn = () => "hello";
cache.set(fn, "greeting function");
console.log(cache.get(fn)); // "greeting function"
Iterating Over a Map
Maps maintain insertion order and are directly iterable:
const fruits = new Map([
["apple", 3],
["banana", 5],
["mango", 2],
]);
// for...of with destructuring
for (const [fruit, count] of fruits) {
console.log(`${fruit}: ${count}`);
}
// apple: 3
// banana: 5
// mango: 2
// Individual iterators
console.log([...fruits.keys()]); // ["apple", "banana", "mango"]
console.log([...fruits.values()]); // [3, 5, 2]
console.log([...fruits.entries()]); // [["apple", 3], ["banana", 5], ["mango", 2]]
Map vs Object — When Each One Wins
This was my biggest question: "Why not just use a regular object?" Here's the honest comparison:
| Feature | Object | Map |
|---|---|---|
| Key types | Strings and Symbols only | Any value (objects, numbers, etc.) |
| Key order | Not guaranteed (mostly works) | ✅ Guaranteed insertion order |
| Size | Manual: Object.keys(obj).length
|
Direct: map.size
|
| Iteration | Indirect (Object.keys/entries) |
Direct (for...of, .forEach) |
| Performance | Slower for frequent add/delete | ✅ Optimized for frequent changes |
| Default keys | Has prototype keys (toString, etc.) | ✅ No default keys — clean |
| JSON support | ✅ Direct JSON.stringify()
|
Needs manual conversion |
| Destructuring/spread | ✅ { ...obj }
|
Not directly |
When to Use Object
- JSON data from APIs
- Configuration objects with known string keys
- When you need destructuring or spread
- Simple, static key-value structures
When to Use Map
- Keys that aren't strings (objects, numbers, DOM elements)
- Frequent additions and deletions
- When insertion order matters
- When you need
.sizeinstead of counting manually - Caching or lookup tables
// ✅ Object — perfect for API data
const user = { name: "Pratham", age: 22, city: "Delhi" };
// ✅ Map — perfect for tracking DOM elements
const clickCounts = new Map();
// clickCounts.set(buttonElement, 0);
// clickCounts.set(linkElement, 0);
What Is a Set?
A Set is a collection of unique values. No duplicates allowed — ever. If you try to add a value that already exists, the Set simply ignores it.
Think of it like a guest list at a party. Each person can only appear once. You can try writing "Pratham" ten times — the list still only shows "Pratham" once.
Creating and Using a Set
const skills = new Set();
skills.add("JavaScript");
skills.add("React");
skills.add("Node.js");
skills.add("JavaScript"); // Duplicate — silently ignored
console.log(skills); // Set(3) {"JavaScript", "React", "Node.js"}
console.log(skills.size); // 3 — not 4!
Set Methods
const colors = new Set(["red", "green", "blue"]);
// Check if a value exists
console.log(colors.has("red")); // true
console.log(colors.has("purple")); // false
// Delete a value
colors.delete("green");
console.log(colors); // Set(2) {"red", "blue"}
// Clear everything
colors.clear();
console.log(colors.size); // 0
Visual: Set Uniqueness
Adding values to a Set:
.add("JS") → Set: { "JS" }
.add("React") → Set: { "JS", "React" }
.add("Node") → Set: { "JS", "React", "Node" }
.add("JS") → Set: { "JS", "React", "Node" } ← IGNORED (duplicate)
.add("React") → Set: { "JS", "React", "Node" } ← IGNORED (duplicate)
.add("Vue") → Set: { "JS", "React", "Node", "Vue" }
Only unique values survive. Duplicates are silently rejected.
The Killer Use Case: Removing Duplicates from an Array
This is the most common reason you'll reach for Set:
const numbers = [1, 3, 5, 3, 7, 1, 9, 5, 3];
// One line — duplicates gone
const unique = [...new Set(numbers)];
console.log(unique); // [1, 3, 5, 7, 9]
Before Set, you'd need a loop with an includes() check or a filter with indexOf. Now it's one line.
// Works with strings too
const tags = ["js", "react", "js", "node", "react", "css"];
const uniqueTags = [...new Set(tags)];
console.log(uniqueTags); // ["js", "react", "node", "css"]
Iterating Over a Set
Sets maintain insertion order and are directly iterable:
const fruits = new Set(["Apple", "Mango", "Banana"]);
for (const fruit of fruits) {
console.log(fruit);
}
// Apple
// Mango
// Banana
// Convert to array anytime
const fruitArray = [...fruits];
console.log(fruitArray); // ["Apple", "Mango", "Banana"]
Set vs Array — When Each One Wins
| Feature | Array | Set |
|---|---|---|
| Duplicates | ✅ Allowed | ❌ Automatically removed |
| Order | ✅ Index-based | ✅ Insertion order |
| Access by index | ✅ arr[0]
|
❌ No index access |
| Check existence |
.includes() — O(n) |
.has() — O(1) ⚡ |
| Size | .length |
.size |
| Methods |
map, filter, reduce, etc. |
add, delete, has
|
| Best for | Ordered lists, transformation | Unique values, fast lookups |
The performance difference in .has() vs .includes() is worth highlighting. For small arrays, you won't notice. But if you're checking whether a value exists in a collection of 10,000 items, Set is dramatically faster because it uses a hash-based lookup (constant time) instead of scanning every element (linear time).
When to Use Array
- You need index-based access
- Order and position matter
- You need
map(),filter(),reduce() - Duplicates are acceptable or expected
When to Use Set
- You need guaranteed uniqueness
- You frequently check "does this value exist?"
- You're removing duplicates from data
- You don't need index-based access
Practical Use Cases
1. Tracking Unique Visitors
const visitors = new Set();
// As users visit
visitors.add("user_101");
visitors.add("user_202");
visitors.add("user_101"); // Returning visitor — not counted again
console.log(`Unique visitors: ${visitors.size}`); // 2
2. Caching API Results with Map
const apiCache = new Map();
const fetchUser = async (userId) => {
if (apiCache.has(userId)) {
console.log("Cache hit!");
return apiCache.get(userId);
}
console.log("Fetching from API...");
// const response = await fetch(`/api/users/${userId}`);
// const data = await response.json();
const data = { id: userId, name: "Pratham" }; // simulated
apiCache.set(userId, data);
return data;
};
3. Counting Word Frequency with Map
const text = "the cat sat on the mat the cat";
const words = text.split(" ");
const frequency = new Map();
for (const word of words) {
frequency.set(word, (frequency.get(word) || 0) + 1);
}
console.log(frequency);
// Map(5) { "the" => 3, "cat" => 2, "sat" => 1, "on" => 1, "mat" => 1 }
4. Finding Common Elements Between Arrays
const arr1 = [1, 2, 3, 4, 5];
const arr2 = [3, 4, 5, 6, 7];
const set2 = new Set(arr2);
const common = arr1.filter((item) => set2.has(item));
console.log(common); // [3, 4, 5]
Using a Set for the lookup makes this O(n) instead of O(n²) — a real performance win for large arrays.
5. Map for Readable Enums / Lookups
const httpStatus = new Map([
[200, "OK"],
[201, "Created"],
[400, "Bad Request"],
[401, "Unauthorized"],
[404, "Not Found"],
[500, "Internal Server Error"],
]);
console.log(httpStatus.get(404)); // "Not Found"
console.log(httpStatus.get(200)); // "OK"
Let's Practice: Hands-On Assignment
Part 1: Map Basics
const studentGrades = new Map();
studentGrades.set("Pratham", "A");
studentGrades.set("Arjun", "B+");
studentGrades.set("Priya", "A+");
console.log(studentGrades.get("Priya")); // "A+"
console.log(studentGrades.has("Rahul")); // false
console.log(studentGrades.size); // 3
for (const [name, grade] of studentGrades) {
console.log(`${name}: ${grade}`);
}
Part 2: Set — Remove Duplicates
const rawData = [10, 20, 30, 20, 40, 10, 50, 30, 60];
const uniqueData = [...new Set(rawData)];
console.log(uniqueData); // [10, 20, 30, 40, 50, 60]
Part 3: Map with Non-String Keys
const config = new Map();
config.set(1, "First Priority");
config.set(true, "Feature Enabled");
config.set({ id: 1 }, "Object Key");
console.log(config.get(1)); // "First Priority"
console.log(config.get(true)); // "Feature Enabled"
console.log(config.size); // 3
Part 4: Set Operations — Union, Intersection, Difference
const setA = new Set([1, 2, 3, 4, 5]);
const setB = new Set([4, 5, 6, 7, 8]);
// Union — all unique values from both
const union = new Set([...setA, ...setB]);
console.log([...union]); // [1, 2, 3, 4, 5, 6, 7, 8]
// Intersection — values in both
const intersection = new Set([...setA].filter((x) => setB.has(x)));
console.log([...intersection]); // [4, 5]
// Difference — values in A but not in B
const difference = new Set([...setA].filter((x) => !setB.has(x)));
console.log([...difference]); // [1, 2, 3]
Key Takeaways
- Map is a key-value collection where keys can be any value — objects, numbers, functions, not just strings. Use it when regular objects fall short.
-
Set is a collection of unique values. Duplicates are automatically rejected. The fastest way to deduplicate an array:
[...new Set(arr)]. -
Map vs Object: Use Map for non-string keys, frequent add/delete, guaranteed order, and
.size. Use Object for JSON data and simple config. -
Set vs Array: Use Set for uniqueness and fast
.has()lookups. Use Array for ordered data with index access and transformation methods. - Both are iterable and maintain insertion order. They work seamlessly with
for...of, spread, and destructuring.
Wrapping Up
Map and Set aren't replacements for objects and arrays — they're complements. Each has its strengths, and knowing when to reach for which one is a sign of a developer who thinks about the right tool for the job, not just the first tool that comes to mind.
I'm learning all of this through the ChaiCode Web Dev Cohort 2026 under Hitesh Chaudhary and Piyush Garg. Map and Set were one of those topics where I initially thought "do I really need these?" — and now I use them regularly, especially Set for deduplication and Map for caching. They're worth learning early.
Connect with me on LinkedIn or visit PrathamDEV.in to follow along. More articles coming as the journey continues.
Happy coding! 🚀
Written by Pratham Bhardwaj | Web Dev Cohort 2026, ChaiCode
Top comments (0)