cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Map & Set

Learn when to choose Map, Set, or a plain object: key types, uniqueness, iteration order, and the new getOrInsert upsert helpers browsers are rolling out.

By the end, you can
  • 01
    Choose the right collectionCompare Maps with plain objects and Sets with arrays.
  • 02
    Use real iteration patternsLoop with entries, keys, values, destructuring, and forEach safely.
  • 03
    Upsert without double workFeature-detect upcoming getOrInsert methods and use reference helpers when needed.

Maps, Sets, and phone contacts

JavaScript already has plain objects and arrays, so why learn two more collections? Because some jobs need clearer rules. A Map is a keyed collection whose keys can be any value. A Set is a collection of values where each value can appear only once.

You have built on object basics, object references, comparisons, iterating arrays, and destructuring. This lesson uses those ideas to model real data: counters, caches, tags, and lookups where the key is not always a string.

Real-life analogyMap is a phone contact list

Think of a Map as a phone contact list. You use a saved key to find one detail. JavaScript lets that key be a string, number, object, or another value.

In real life: A contact can use any saved key
In JavaScript: A Map key can be any JavaScript value
In real life: Each key finds one contact detail
In JavaScript: Each key points to one value
In real life: New contacts keep their added order
In JavaScript: Map iteration follows insertion order

Where the analogy stops: A phone contact list uses names and numbers. Map also allows object keys, which match only when they are the same object.

Real-life analogySet is a guest list

A Set is the bouncer’s list: if Ada is already on it, adding Ada again does not create a second Ada. That makes Sets perfect when membership and uniqueness matter more than positions.

In real life: Writing a name twice still means one guest
In JavaScript: Adding the same value twice keeps one Set entry
In real life: You can ask if a name is on the list
In JavaScript: Use set.has(value)
In real life: The first time a guest was added is remembered
In JavaScript: Set iteration preserves insertion order

Where the analogy stops: A guest list may merge two people with the same name. A Set only merges values JavaScript considers the same. Two separate {} objects both stay.

Definition

A Map stores key-value entries with any key type. A Set stores unique values. Both are iterable and preserve insertion order.

Map vs plain objects

INTERACTIVE

A plain object is still the best shape for many records: a user profile, a product returned from an API, or settings you plan to save as JSON. But object property keys are strings or symbols. If you write obj[1], the property name is really "1". If you write obj[], the property name is usually "[object Object]".

A Map does not do that conversion. It keeps the number 1, the string "1", an object, and NaN as distinct keys. It also avoids inherited names: "toString" is not accidentally one of your Map entries.

Watch object keys collide, then Map keeps them
Step 0 of 13Ready
Your turn: follow the blue line

Predict which keys collide in the object, then step through the same keys in a Map.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
const obj = {};obj[1] = "number one";obj["1"] = "string one";obj[key] = "object key";obj[NaN] = "not a number"; const map = new Map();map.set(1, "number one");map.set("1", "string one");map.set(key, "object key");map.set(NaN, "not a number"); console.log(Object.keys(obj));console.log(map.size);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Compare the real object and Map results
Same keys, two collectionsPop out in the code editor (opens in a new tab)JavaScript
const key = { id: 1 };const obj = {};obj[1] = "number one";obj["1"] = "string one";obj[key] = "object key";obj[NaN] = "not a number"; const map = new Map();map.set(1, "number one");map.set("1", "string one");map.set(key, "object key");map.set(NaN, "not a number"); console.log(Object.keys(obj));console.log(map.size);
Real resultMap
size4
entries[["1","number one"],["\"1\"","string one"],["{ id: 1 }","object key"],["NaN","not a number"]]
has("toString")false
Try it yourself

Map keeps 1, "1", the object, and NaN as four distinct keys.

The code is fixed; the toggle only changes which real result is emphasized.
Plain object or Map?
Plain objectMap
Key typesStrings and symbols only; other keys are convertedAny value: objects, functions, numbers, strings, NaN, and more
OrderOwn property order follows object property rulesIteration is insertion order
SizeUse Object.keys(obj).length for enumerable string keysUse map.size
IterationUse Object.keys, values, entries, or for...in carefullyDirectly iterable with for...of, .keys(), .values(), .entries()
JSONNatural with JSON.stringify(obj)Convert first; Maps do not serialize entries directly
Frequent add/deleteFine for records, less clear for dynamic dictionariesDesigned for dynamic keyed collections
When in doubt

Use an object for a fixed record with known property names. Reach for Map when entries are added and removed often, when you need non-string keys, or when the thing really is a lookup table rather than a record.

Set for uniqueness

STEP THROUGH

A Set answers one question very well: “Have I seen this value already?” Creating a Set from an array removes duplicates while keeping the first time each value appeared: [...new Set(array)] is a common dedupe pattern.

Step through Set uniqueness
Step 0 of 5Ready
Your turn: follow the blue line

Step through a Set and watch duplicates disappear without sorting the values.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
const unique = new Set(list);unique.add(3).add(4);console.log(unique.size);console.log([...unique]);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Choose the Set case to replay

Changing the case starts a fresh replay with real Set behavior.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Set uses the SameValueZero comparison you saw near Comparisons: NaN equals NaN here, and 0 and -0 count as the same value. Objects still compare by reference. Two separate object literals are two different values, even if both print as .

Everyday Set movesPop out in the code editor (opens in a new tab)JavaScript
const seen = new Set(["red", "red", "blue"]);console.log(seen.size);       // 2console.log(seen.has("red")); // trueseen.add("green").add("red"); // add returns the Setconsole.log(seen.delete("red")); // trueconsole.log([...seen]);       // ["blue", "green"]

Iterating Map & Set

STEP THROUGH

Map and Set are iterable, which means for...of, spread, and destructuring work naturally. For a Map, the default iterator yields entries, so this destructuring shape is common: for (const [key, value] of map).

Iterate a Map, then build a Set
Step 0 of 8Ready
Your turn: follow the blue line

Follow the order: Map entries, Map forEach's value-key callback, then a Set made from keys.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
  ["Ada", 3],  ["Lin", 5],]); for (const [name, score] of scores) {  console.log(name + ": " + score);} scores.forEach((value, key) => {  console.log(key + " scored " + value);}); const names = new Set(scores.keys());console.log([...names].join(", "));
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Useful conversions go both ways. new Map(Object.entries(obj)) turns object entries into a Map. Object.fromEntries(map) turns a Map with property-friendly keys back into an object. [...set] and [...map.keys()] create arrays when array methods are more convenient.

forEach gotcha

Map forEach passes (value, key), not (key, value). In for...of, destructuring an entry uses [key, value]. The two forms look similar, so say the order out loud while learning.

getOrInsert & getOrInsertComputed (upsert)

INTERACTIVE

“Upsert” means “give me the existing entry, or insert one if it is missing.” The Map upsert proposal adds Map.prototype.getOrInsert and Map.prototype.getOrInsertComputed. They are a recent addition to the language that browsers are rolling out, so this lesson feature-detects them after the page mounts and uses a small reference helper when they are not available.

Feature-detect upsert, then compare word counters
Reference helper stylePop out in the code editor (opens in a new tab)JavaScript
const counts = new Map();for (const word of words) {  const box = getOrInsertComputed(counts, word, () => ({ count: 0 }));  box.count += 1;}console.log([...counts]);
Support and helper resultsupsert
native getOrInsertchecking
native getOrInsertComputedchecking
helper entries[["theme","dark"],["letters",7]]
computed callback calls["letters"]
Try it yourself

Checking support after mount avoids a server/browser hydration mismatch.

The TC39 upsert methods are a recent addition to the language that browsers are rolling out. The lesson does not assume they exist.

The semantics are the important part: getOrInsert(map, key, value) sets value only when key is missing, then returns map.get(key). getOrInsertComputed(map, key, callback) calls the callback only when the key is missing, and it passes the key to the callback. Existing entries are returned without recomputing.

Reference helpers used when native upsert is missingPop out in the code editor (opens in a new tab)JavaScript
function getOrInsert(map, key, value) {  if (!map.has(key)) map.set(key, value);  return map.get(key);} function getOrInsertComputed(map, key, callback) {  if (!map.has(key)) map.set(key, callback(key));  return map.get(key);} const cache = new Map();getOrInsert(cache, "theme", "dark");getOrInsert(cache, "theme", "light");getOrInsertComputed(cache, "letters", key => key.length);console.log([...cache]); // Helper proof: same=10, added=5

Where you’ll use this

INTERACTIVE

Maps and Sets are everyday tools for shaping data before rendering it. A search page might keep a Set of selected filter IDs. An analytics panel might count events in a Map. A component library might cache measurements by object reference. A form might convert a Map to an object before saving.

Count words with a Map
Word counterPop out in the code editor (opens in a new tab)JavaScript
const counts = new Map();for (const word of words) {  const current = counts.get(word) ?? 0;  counts.set(word, current + 1);}console.log([...counts]);
Counts2 unique
map3
set1
Try it yourself

The Map has 2 unique word keys. Repeated words update the existing count instead of adding another entry.

The order is the first time each word appeared. That makes a Map useful for readable reports.
  • Word counters: Map from word to count, preserving first-seen order.
  • De-duplication: Set from an array, then spread back to an array.
  • Object-keyed caches: Map while values should stay strongly reachable; WeakMap & WeakSet comes later.
  • Serialization boundary: convert with entries before JSON. The JSON lesson explains why Maps do not serialize directly.

Map, Set, or plain object?

SORT

Choosing the structure is usually about the question you need to ask: “what fields describe this thing?”, “what value belongs to this key?”, or “have I seen this value already?” Sort the scenarios below.

Choose the collection
  • A JSON-shaped user record with fixed fields
  • Cache data by a DOM node or other object
  • Keep a list of tags without duplicates
  • Count words as word → count pairs
  • App settings loaded and saved as JSON
  • Remember which product IDs have appeared
Try it yourself
0 of 6 correct

Sort each scenario into the structure that fits best.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common misconceptions

  • “Map is just a fancier object.” Map entries are not properties; non-string keys stay intact and Object.keys(map) does not list entries.
  • “Set compares objects by shape.” It compares object references. Two literals are different values.
  • “Set sorts things.” Set removes duplicates but preserves insertion order; it does not sort.
  • “JSON.stringify(new Map()) saves entries.” It does not. Convert first with arrays or Object.fromEntries when keys are strings.
  • “getOrInsertComputed always runs the callback.” It runs only for missing keys. That is the whole cache-friendly point.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upDedupe an array with Set

Predict the printed result.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const ids = [7, 7, 2, 9, 2];
console.log([...new Set(ids)].join(","));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeCount occurrences with a Map

    Run the counter mentally and enter the two console lines.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const counts = new Map();
    for (const name of ["Ada", "Lin", "Ada"]) {
      counts.set(name, (counts.get(name) ?? 0) + 1);
    }
    console.log(counts.get("Ada"));
    console.log(counts.size);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeConvert a Map to an object

      What exact JSON string is printed?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const map = new Map([["theme", "dark"], ["page", "home"]]);
      console.log(JSON.stringify(Object.fromEntries(map)));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticePredict mixed Map keys

        Predict map.size after the inserts.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const key = {};
        const map = new Map([[1, "number"], ["1", "string"], [key, "object"], [NaN, "nan"], [NaN, "again"]]);
        console.log(map.size);

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeImplement a cache with computed upsert

          Use the helper semantics to predict the two console lines.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const calls = [];
          function getOrInsertComputed(map, key, callback) {
            if (!map.has(key)) map.set(key, callback(key));
            return map.get(key);
          }
          const cache = new Map();
          getOrInsertComputed(cache, "Ada", name => { calls.push(name); return name.length; });
          getOrInsertComputed(cache, "Ada", name => { calls.push(name); return 99; });
          console.log(cache.get("Ada"));
          console.log(calls.length);

          Answer, then press Check. Spacing and letter case don’t matter.

            Check your understanding

            7 QUESTIONS
            Map & Set quiz · 7 questionsScore: first tries count
            1. Question 1 of 7Why does new Map([[1, "a"], ["1", "b"]]).size equal 2?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does the Set with duplicate NaN values print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const values = new Set([NaN, NaN, 1, 1]);
              console.log(values.size);

              Choose an answer to see the explanation.

            3. Question 3 of 7What is the callback argument order for map.forEach?

              Choose an answer to see the explanation.

            4. Question 4 of 7What does the Set of object references print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const a = {};
              const b = {};
              const set = new Set([a, b, a]);
              console.log(set.size);

              Choose an answer to see the explanation.

            5. Question 5 of 7What do Object.keys(map).length and map.size print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const map = new Map([["a", 1], ["b", 2]]);
              console.log(Object.keys(map).length);
              console.log(map.size);

              Choose an answer to see the explanation.

            6. Question 6 of 7When should getOrInsertComputed(map, key, callback) call callback?

              Choose an answer to see the explanation.

            7. Question 7 of 7Which conversion turns a Map with string keys into a plain object?

              Choose an answer to see the explanation.

            Key takeaways

            • Use Map for dynamic key-value entries, especially when keys are not strings.
            • Use Set for membership and uniqueness; spread it back to an array when needed.
            • Both Map and Set preserve insertion order and work with for...of.
            • Upsert helpers return the existing value or insert a missing one; computed upsert calls its callback only when missing.
            • Convert Maps intentionally at boundaries such as objects, arrays, and JSON.

            One-liner: Map answers “what value belongs to this key?”, Set answers “have I seen this value?”, and objects answer “what named fields describe this record?”

            Up next: Set operations.

            Back to the Working with data stage.

            CompleteFrontend Clear concepts. Working examples.