JavaScript • Collections and Strings

Map & Set — Use Map and Set for key/value and membership (intro)

Learn how Map stores key-value pairs and Set tracks unique values for cleaner, faster JavaScript code.

JavaScript emblem with code brackets, braces, and a code file.
Inside this lesson 10 sections 5 questions 6 figures

SECTION 1

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.

1995 • JavaScript Is Born

Brendan Eich creates JavaScript in 10 days at Netscape. Plain objects ({}) are the only way to map keys to values.

2009 • ES5 Standardized

ES5 adds JSON and strict mode, but still no dedicated Map or Set structures. Developers rely on libraries like Underscore.js for collection utilities.

2011 • Early Proposals

TC39 (the committee that designs JavaScript) drafts proposals for Map and Set, inspired by similar structures in Python, Java, and C++.

2015 • ES6 (ES2015) Ships Map & Set

The landmark ES6 specification introduces Map, Set, WeakMap, and WeakSet as built-in keyed collections.

2020+ • Widespread Adoption

Map and Set are supported in all modern browsers and Node.js. They appear routinely in frameworks like React, Vue, and server-side JavaScript.

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.

SECTION 2

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.

1

Any-Type Keys (Map)

Unlike plain objects, a Map lets you use objects, numbers, booleans, or any value as a key—not just strings.
2

Guaranteed Uniqueness (Set)

Adding a duplicate value to a Set is silently ignored, keeping every element unique without extra code.
3

Insertion-Order Iteration

Both Map and Set remember the order you added entries, so looping through them always follows insertion order.
4

Built-in Size Property

Both provide a .size property that instantly returns the number of entries—no manual counting needed.
5

Fast Lookups

Checking if a key exists in a Map or a value exists in a Set is an O(1) operation on average, much faster than searching an array.
✦

KEY TAKEAWAY

Think of a Map like a phone's contact list: each contact name (key) points to a phone number (value), and you can look up anyone instantly. A Set is like a guest list at a party: it only cares whether a name is on the list or not—no duplicates allowed, and it doesn't store extra info for each name.

SECTION 3

Visual Explanation

MapKey → Value pairs"name""Alice"42"answer"{obj}[1, 2, 3]KEYSVALUES.size → 3.get("name") → "Alice"SetUnique values only"a""b""c"742DUPLICATE REJECTEDset.add("a") → still size 5.size → 5.has("b") → true.has("z") → false
Left: a Map stores key-value pairs where keys can be any type (strings, numbers, objects). Right: a Set stores unique values and silently ignores duplicate additions.

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.

SECTION 4

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 / PropertyWhat It DoesReturns
new Map()Creates an empty MapMap instance
.set(key, val)Adds or updates a key-value pairThe Map itself (chainable)
.get(key)Retrieves the value for a keyThe value, or undefined
.has(key)Checks if a key existstrue or false
.delete(key)Removes a key-value pairtrue if the key existed
.sizeReturns the number of entriesA number
.clear()Removes all entriesundefined

Common Map methods and properties

Set API at a Glance

Method / PropertyWhat It DoesReturns
new Set()Creates an empty SetSet instance
.add(value)Adds a value (ignores duplicates)The Set itself (chainable)
.has(value)Checks if a value existstrue or false
.delete(value)Removes a valuetrue if the value existed
.sizeReturns the number of valuesA number
.clear()Removes all valuesundefined

Common Set methods and properties

⚠

Equality Rules

Map and Set use an algorithm called SameValueZero to compare keys/values. It works like === with one exception: NaN is considered equal to NaN. Two different objects are never equal, even if they have the same properties.

SECTION 5

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.

Feature ComparisonPlain Object { }✓ String / Symbol keys only✗ No .size — use Object.keys().length✗ Prototype pollution risk✓ JSON.stringify works directly~ Iteration order mostly insertion-basedMap✓ Any type as key✓ .size property built in✓ No prototype keys to worry about✗ Not directly JSON-serializable✓ Guaranteed insertion-order iterationArray [ ]✓ Ordered, indexed access✗ .includes() is O(n) — slow for large lists✗ Allows duplicate values✓ Rich API: map, filter, reduce✓ JSON.stringify works directlySet✓ Guarantees unique values✓ .has() is O(1) — fast lookup✓ Automatically rejects duplicates✗ No index-based access✗ Not directly JSON-serializable
Top row compares Object vs. Map for key-value storage. Bottom row compares Array vs. Set for membership checking. ✓ = advantage, ✗ = limitation, ~ = partial.

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.

SECTION 6

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.

Word Frequency Counter

  1. 1

    Step 1 — Split the sentence into words

    Start with a sample sentence and split it into an array of lowercase words using .toLowerCase().split(" ").
    const words = "the cat sat on the mat".toLowerCase().split(" "); → ["the", "cat", "sat", "on", "the", "mat"]
  2. 2

    Step 2 — Create a Map to store frequencies

    Initialize an empty Map. Loop through each word: if the Map already has that word as a key, increment its count by 1. Otherwise, set its count to 1.
    const freq = new Map(); for (const w of words) { freq.set(w, (freq.get(w) || 0) + 1); }
  3. 3

    Step 3 — Inspect the Map

    After the loop, the Map contains each unique word as a key and its count as the value. The word "the" appears twice, while all other words appear once.
    freq.get("the") // → 2 freq.get("cat") // → 1 freq.size // → 5
  4. 4

    Step 4 — Create a Set of unique words

    Pass the array directly into the Set constructor. Duplicates are automatically removed.
    const unique = new Set(words); // Set {"the", "cat", "sat", "on", "mat"} unique.size // → 5
  5. 5

    Step 5 — Convert Set back to an Array (if needed)

    Use the spread operator or Array.from() to turn the Set back into a regular array for further processing.
    const uniqueArr = [...unique]; // ["the", "cat", "sat", "on", "mat"]
💡

PATTERN TO REMEMBER

The expression (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.

SECTION 7

Strengths & Limitations

FeatureStrengthLimitation
Key flexibility (Map)Any value—objects, functions, primitives—can be a keyTwo different object references that look identical are treated as different keys
Uniqueness (Set)Automatic deduplication with no extra logicCannot store two objects with the same content—they must be the same reference
PerformanceO(1) average time for add, get, has, and delete operationsSlightly more memory overhead than plain arrays for small collections
IterationGuaranteed insertion-order; works with for...of and forEachNo direct index access (no map[0]) — must iterate or convert to array
SerializationCan be converted to arrays easily via spread or Array.fromJSON.stringify does not work directly — you must convert first

Map & Set: Strengths vs. Limitations

🎯

WHEN TO REACH FOR MAP & SET

Use a Map whenever you need a dictionary with non-string keys, frequent additions/removals, or a reliable count of entries. Use a Set whenever you need to track "have I seen this before?" or remove duplicates from a list. For simple, static config objects or ordered indexed data, plain objects and arrays still work great.

SECTION 8

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.

FeatureMap / SetWeakMap / WeakSet
Key / value typesAny value can be a key (Map) or member (Set)Keys (WeakMap) / members (WeakSet) must be objects or symbols
Garbage collectionEntries persist as long as the Map/Set existsEntries are garbage-collected if no other reference to the key/value exists
Iterable?Yes — supports for...of, .forEach, spreadNo — cannot iterate or check .size
Use caseGeneral-purpose dictionaries and membership trackingPrivate metadata on objects without preventing garbage collection

Standard vs. Weak collections

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.

SECTION 9

Practice Problems

Question 1: CONCEPTUAL

Explain in your own words why you might choose a Map over a plain JavaScript object. Give at least two reasons.
Show answer
A Map allows any data type to serve as a key, not just strings and Symbols. This is crucial when you want to associate data with DOM elements, other objects, or numeric IDs without converting them to strings. Additionally, Map provides a built-in .size property for instant entry counting, whereas with objects you must use Object.keys(obj).length. Map also avoids prototype pollution — keys like "constructor" or "toString" won't conflict with inherited properties.

Question 2: BASIC CALCULATION

What will the following code output? Trace through it step by step. 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));
Show answer
The Set constructor receives an array with 10 elements, but duplicates (1, 5, and 3) are automatically removed. The unique values are 3, 1, 4, 5, 9, 2, and 6, giving s.size → 7. The call s.has(4) returns true because 4 is in the Set, and s.has(7) returns false because 7 was never added.

Question 3: INTERMEDIATE

Write a function 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.
Show answer
Here is one clean solution: function countChars(str) { const map = new Map(); for (const ch of str) { map.set(ch, (map.get(ch) || 0) + 1); } return map; } The for...of loop iterates over each character. For each one, we use the counting pattern: (map.get(ch) || 0) + 1. If the character hasn't been seen, .get() returns undefined (falsy), so the count starts at 0 + 1 = 1. On subsequent encounters it increments the existing count.

Question 4: APPLIED

You're building a social media app and need to track which users have liked a post. Each user can only like a post once. If a user tries to like a post again, their like should be removed (toggle behavior). Which data structure would you use and why? Write a function toggleLike(likes, userId) that implements this.
Show answer
A Set is ideal because it enforces uniqueness and provides O(1) .has(), .add(), and .delete(). Here is the implementation: function toggleLike(likes, userId) { if (likes.has(userId)) { likes.delete(userId); } else { likes.add(userId); } return likes; } // Usage: const postLikes = new Set(); toggleLike(postLikes, "user42"); // liked toggleLike(postLikes, "user42"); // unliked Using an array would work but would require .indexOf() (O(n)) to check membership and .splice() to remove, both slower than the Set approach.

Question 5: CRITICAL THINKING

Consider the code below. Why does the second .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" })); // false
Show answer
Map uses the SameValueZero algorithm for key comparison, which means objects are compared by reference, not by their contents. The { name: "Alice" } in the .set() call and the one in the .has() call are two different object instances in memory, even though they have identical properties. To fix this, you should store the object reference in a variable: const alice = { name: "Alice" }; m.set(alice, 100); console.log(m.has(alice)); // true Alternatively, if you need to look up by content, use a string key like JSON.stringify({ name: "Alice" }) or use a unique identifier (like an ID number) as the key instead of the object itself.

SUMMARY

Lesson Summary

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.

Varsity Tutors • JavaScript • Map & Set — Use Map and Set for key/value and membership (intro)