Historical Context & Motivation
Before Map and Set arrived in JavaScript, developers had only one tool for grouping data under keys: the plain object ({}). While objects work fine for simple cases, they were never designed to be general-purpose dictionaries. Keys had to be strings (or Symbols), there was no built-in way to count entries, and prototype properties could accidentally collide with your data. As web applications grew larger and more complex, these limitations became a real source of bugs.
Similarly, checking whether a value was "already in the collection" meant scanning an array with indexOf or building awkward objects where every value was stored as a key mapped to true. Neither approach was elegant or fast. The JavaScript community needed purpose-built data structures for these two extremely common tasks: associating keys with values and tracking membership in a group of unique items.
{}) are the only way to map keys to values.JSON and strict mode, but still no dedicated Map or Set structures. Developers rely on libraries like Underscore.js for collection utilities.The central question that Map and Set answer is straightforward: How can JavaScript give developers fast, flexible collections that go beyond what plain objects and arrays offer? The rest of this lesson explores that answer.
Core Principles & Definitions
At the heart of this topic are two built-in classes that each solve a distinct problem. A Map is a collection of key-value pairs where any data type—strings, numbers, objects, even functions—can serve as a key. A Set is a collection of unique values, meaning it automatically prevents duplicates. Together they cover two of the most common needs in programming: looking something up by a key and checking whether something is already in a group.
Any-Type Keys (Map)
Guaranteed Uniqueness (Set)
Insertion-Order Iteration
Built-in Size Property
.size property that instantly returns the number of entries—no manual counting needed.Fast Lookups
Visual Explanation
In the diagram above, notice that the Map's keys include a string, a number, and even an object reference—something a plain JavaScript object cannot do. On the Set side, the value "a" is only stored once; attempting to add it again has no effect. Both structures expose a .size property and fast lookup methods (.get() for Map, .has() for Set) that return results in constant time on average.
How Map & Set Work Under the Hood
JavaScript engines implement Map and Set using a technique called hash tables (sometimes combined with linked lists for ordering). When you call map.set(key, value), the engine computes a hash of the key—a numeric fingerprint—that points to a slot in an internal array. This is what makes lookups so fast: instead of scanning every entry, the engine jumps directly to the right slot.
Map API at a Glance
| Method / Property | What It Does | Returns |
|---|---|---|
new Map() | Creates an empty Map | Map instance |
.set(key, val) | Adds or updates a key-value pair | The Map itself (chainable) |
.get(key) | Retrieves the value for a key | The value, or undefined |
.has(key) | Checks if a key exists | true or false |
.delete(key) | Removes a key-value pair | true if the key existed |
.size | Returns the number of entries | A number |
.clear() | Removes all entries | undefined |
Set API at a Glance
| Method / Property | What It Does | Returns |
|---|---|---|
new Set() | Creates an empty Set | Set instance |
.add(value) | Adds a value (ignores duplicates) | The Set itself (chainable) |
.has(value) | Checks if a value exists | true or false |
.delete(value) | Removes a value | true if the value existed |
.size | Returns the number of values | A number |
.clear() | Removes all values | undefined |
=== with one exception: NaN is considered equal to NaN. Two different objects are never equal, even if they have the same properties.Map vs. Object & Set vs. Array
A natural question is: "Why not just use a plain object or an array?" The answer depends on your use case. The diagrams and table below highlight the key differences so you can make an informed choice.
The diagram makes the trade-offs clear. Choose a Map when you need non-string keys, frequent additions and deletions, or a reliable .size. Choose a Set when you need to eliminate duplicates or quickly test whether a value is in your collection. Stick with plain objects for simple config-style data and arrays when you need indexed, ordered data with rich transformation methods.
Worked Example — Counting Word Frequencies
Let's walk through a realistic problem: given a sentence, count how many times each word appears using a Map, and collect the unique words using a Set.
.toLowerCase().split(" ").const words = "the cat sat on the mat".toLowerCase().split(" "); → ["the", "cat", "sat", "on", "the", "mat"]const freq = new Map();
for (const w of words) {
freq.set(w, (freq.get(w) || 0) + 1);
}freq.get("the") // → 2
freq.get("cat") // → 1
freq.size // → 5const unique = new Set(words);
// Set {"the", "cat", "sat", "on", "mat"}
unique.size // → 5Array.from() to turn the Set back into a regular array for further processing.const uniqueArr = [...unique];
// ["the", "cat", "sat", "on", "mat"](map.get(key) || 0) + 1 is a classic JavaScript pattern for counting. If the key doesn't exist yet, .get() returns undefined, which is falsy, so || 0 provides a default of zero before adding one.Strengths & Limitations
| Feature | Strength | Limitation |
|---|---|---|
| Key flexibility (Map) | Any value—objects, functions, primitives—can be a key | Two different object references that look identical are treated as different keys |
| Uniqueness (Set) | Automatic deduplication with no extra logic | Cannot store two objects with the same content—they must be the same reference |
| Performance | O(1) average time for add, get, has, and delete operations | Slightly more memory overhead than plain arrays for small collections |
| Iteration | Guaranteed insertion-order; works with for...of and forEach | No direct index access (no map[0]) — must iterate or convert to array |
| Serialization | Can be converted to arrays easily via spread or Array.from | JSON.stringify does not work directly — you must convert first |
Connection to Advanced Structures
Map and Set are the foundational keyed collections in JavaScript, but ES6 also introduced their "weak" counterparts: WeakMap and WeakSet. Understanding the standard versions prepares you for these more advanced structures.
| Feature | Map / Set | WeakMap / WeakSet |
|---|---|---|
| Key / value types | Any value can be a key (Map) or member (Set) | Keys (WeakMap) / members (WeakSet) must be objects or symbols |
| Garbage collection | Entries persist as long as the Map/Set exists | Entries are garbage-collected if no other reference to the key/value exists |
| Iterable? | Yes — supports for...of, .forEach, spread | No — cannot iterate or check .size |
| Use case | General-purpose dictionaries and membership tracking | Private metadata on objects without preventing garbage collection |
As you continue in JavaScript, you'll also encounter libraries that build on these primitives. For example, the Immutable.js library provides persistent data structures with Map and Set semantics that never mutate in place. Frameworks like React use Sets internally to track which components need re-rendering. Mastering the built-in Map and Set gives you a strong foundation for all of these tools.
Practice Problems
const s = new Set([3, 1, 4, 1, 5, 9, 2, 6, 5, 3]);
console.log(s.size);
console.log(s.has(4));
console.log(s.has(7));countChars(str) that takes a string and returns a Map where each key is a character and each value is the number of times that character appears. For example, countChars("hello") should return a Map with entries: h→1, e→1, l→2, o→1.toggleLike(likes, userId) that implements this..has() return false, even though the objects look identical? How would you redesign this to make it work?
const m = new Map();
m.set({ name: "Alice" }, 100);
console.log(m.has({ name: "Alice" })); // falseLesson Summary
JavaScript's Map is a collection of key-value pairs that accepts any data type as a key, provides a built-in .size property, and guarantees insertion-order iteration. Use .set() to add entries, .get() to retrieve values, and .has() to check for a key—all in O(1) average time.
JavaScript's Set stores unique values and automatically rejects duplicates. It is perfect for membership testing (.has()) and deduplication (pass an array to new Set(arr)). Both Map and Set compare entries using SameValueZero, meaning objects are compared by reference—keep this in mind to avoid subtle bugs. Together, these two collections fill the gaps that plain objects and arrays leave, making your JavaScript code cleaner, faster, and more expressive.