completefrontend
Open lab My progress
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 collectionPick a Map or a plain object, and a Set or an array, for a job.
  • 02
    Loop over Maps and SetsUse for...of, keys, values, entries, destructuring, and forEach without mixing up the order of key and value.
  • 03
    Get a value or add it in one stepCheck whether the browser has the new getOrInsert methods, and use a small helper when it doesn't.

Maps, Sets, and phone contacts

JavaScript already has objects and arrays. This lesson adds two more ways to keep a group of values: Map and Set. Each one is a collection, which means a container that holds many values.

  • A Map keeps pairs. Each pair has a key, the thing you look up, and a value, the thing you get back. The key can be any value: a string, a number, even an object.
  • A Set keeps single values. Each value can be in it only once.

This lesson uses ideas from earlier lessons: objects, object references, comparisons, loops over arrays, and destructuring. You will use Maps and Sets to count words, to remember what you have already seen, and to look things up.

Real-life analogyMap is a phone contact list

Think of the contact list on your phone. You find a person by the name you saved, and you get one phone number back. A Map works the same way: you give it a key, and it gives you one value.

In real life: You find a contact by their saved name
In JavaScript: You find a Map value by its key
In real life: Each name gives you one phone number
In JavaScript: Each key gives you one value
In real life: The "recently added" list shows contacts in the order you saved them
In JavaScript: A Map keeps its pairs in the order you added them
Real-life analogySet is a guest list

A party has a guest list. If Ada is already on the list, writing Ada again does not add a second Ada. You can also ask, "Is Ada on the list?" The list cares about who is on it, not about who is first or third.

In real life: Writing a name twice still means one guest
In JavaScript: Adding the same value twice keeps one copy in the Set
In real life: You can ask if a name is on the list
In JavaScript: set.has(value) answers true or false
In real life: Names stay in the order they were first written
In JavaScript: A Set keeps values in the order you first added them
Definition

A Map stores key-value pairs, and a key can be any value. A Set stores values, each one only once. You can loop over both with for...of, and both keep the order in which you added things.

Look up a price in a MapPop out in the code editor (opens in a new tab)JavaScript
const prices = new Map([["tea", 3], ["coffee", 5]]);
console.log(prices.get("tea"));
console.log(prices.size);

Line 1 makes a Map with two pairs. new Map(...) takes a list of [key, value] pairs. Here the key "tea" goes with 3, and the key "coffee" goes with 5. Each pair in a Map is also called an entry.

Line 2 calls get("tea"), which looks up the key "tea". It prints 3. Line 3 reads size, the number of entries in the Map. It prints 2.

The prices Map. A key tag "tea" points to the value 3. A key tag "coffee" points to the value 5. prices.get("tea") gives 3.keyvalue"tea"3"coffee"5prices.get("tea") → 3
Each pair in the Map is a key and a value. get takes a key, follows it, and gives back that key's value.
Check for a tag in a SetPop out in the code editor (opens in a new tab)JavaScript
const tags = new Set(["news", "news", "sale"]);
console.log(tags.has("sale"));
console.log(tags.size);

Line 1 makes a Set from an array. The array has "news" twice, but the Set keeps it only once. Line 2 calls has("sale"), which asks "is this value in the Set?" It prints true. Line 3 prints 2, because the Set holds two different values.

Three cards, "news", "news", and "sale", go into a Set called tags. The first "news" and "sale" go in. The second "news" is already in, so it is skipped. The Set holds two values."news""news""sale"already in"news""sale"tags
The array has "news" twice. The Set already holds "news", so the second copy is skipped. Two values are left.

So a Map gives you back a value for a key. A Set only answers yes or no: is this value in it?

Map vs plain objects

INTERACTIVE

A plain object is still the best choice for many things. Use one for a record: one thing with fields you know ahead of time. A user profile, a product from a server, and settings you save as JSON are all records.

But an object's keys can only be strings or symbols. (A symbol is a special kind of key that is always unique. You won't need symbols here.) Any other key is turned into a string first:

  • obj[1] uses the key "1", which is a string.
  • obj[] usually uses the key "[object Object]".

A Map does not change its keys. The number 1, the string "1", an object, and NaN are four different keys.

Left: the keys 1 and "1" go into a plain object called obj. Both become the string "1", so obj has one key. Right: the same two keys go into a Map. It keeps both, so the Map has two keys.1"1""1"objone key1"1"1"1"maptwo keys
A plain object turns the number 1 into the string "1", so both keys land on the same spot. A Map keeps each key as it is.

A Map also has no hidden keys. An object borrows some names, like toString, from its prototype, the parent object it gets shared methods from. A Map holds only the entries you add, so map.has("toString") is false.

Watch object keys land on the same spot, then see a Map keep them apart
Step 0 of 13Ready
Your turn: follow the blue line

Guess which object keys land on the same spot. Then step through the same keys in a Map.

Running in
  1. script
Next: line 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);
CallStoreChangeResultBlue line: runs next. 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

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

The code stays the same. The button only switches which real result you see.

The table below compares the two. Read each row as: for this question, here is the object's answer, and here is the Map's answer.

Plain object or Map: how each one does the same job
Your questionPlain objectMap
Which keys can I use?Only strings and symbols. Any other key is turned into a string.Any value: objects, functions, numbers, strings, NaN, and more.
In what order do I get the keys back?In the object's own order. Number-like keys come first, from small to big. Other keys keep the order you added them.In the order you added the entries.
How do I count the entries?Use Object.keys(obj).length. It counts the object's own string keys that loops can see.Read map.size.
How do I loop over it?Use Object.keys, Object.values, or Object.entries. A for...in loop also works, but it can show keys from the prototype too.Loop over the Map itself with for...of, or use .keys(), .values(), or .entries().
Can I save it as JSON?Yes. JSON.stringify(obj) works right away.Not directly. Turn it into an object or an array first.
Will I add and remove keys often?It works, but a list that keeps changing is harder to follow in an object.Yes. A Map is made for keys that come and go.
A Map can store zeroPop out in the code editor (opens in a new tab)JavaScript
const stock = new Map([["tea", 4]]);
stock.set("coffee", 0);
console.log(stock.has("coffee"));
console.log(stock.get("coffee"));

Line 1 makes a Map with one entry. Line 2 adds the key "coffee" with the value 0. Line 3 calls has("coffee") and prints true, because the key is there. Line 4 calls get("coffee") and prints 0.

Watch out: 0 counts as false in an if. A value like that is called falsy; 0, "", and undefined are falsy. So to ask "is this key here?", use has. Don't check whether get gave back something that counts as true.

Which one to pick

Use an object for one record with field names you know ahead of time. Use a Map when you add and remove entries often, when your keys are not strings, or when you are building a lookup table: a list where you find a value by its key.

A Set keeps each value once

STEP THROUGH

A Set answers one question very well: "Have I seen this value before?"

When you make a Set from an array, the repeats disappear. Each value stays where it first appeared. Removing repeats like this is often called deduping. Here is a common way to dedupe an array: [...new Set(array)]. The ... is spread syntax. It takes the values out of the Set and puts them into a new array.

Step through a Set that skips repeats
Step 0 of 5Ready
Your turn: follow the blue line

Step through a Set and watch the repeats disappear. The values are not sorted.

Running in
  1. script
Next: line 1
const unique = new Set(list);unique.add(3).add(4);console.log(unique.size);console.log([...unique]);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Choose which Set code to step through

Picking a different case starts the steps again from the top.

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.

How does a Set decide that two values are the same? It uses a rule called SameValueZero, which you met in the Comparisons lesson. It is almost the same as ===:

  • NaN counts as equal to NaN. With ===, it does not.
  • 0 and -0 count as the same value, just like with ===.
  • Objects are compared by reference, which means by which object it is, not by what is inside. Two separate {} objects are two different values, even though they print the same.
Everyday Set methodsPop 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"]

Line 1 makes a Set that holds "red" once and "blue" once. Then, line by line:

  • Line 2: size counts the values. It prints 2.
  • Line 3: has checks whether a value is in the Set. It prints true.
  • Line 4: add puts a value in and gives back the same Set. So you can call add again right after it. Adding "red" again changes nothing.
  • Line 5: delete removes a value. It prints true because it found one to remove.
  • Line 6: spreading the Set gives ["blue", "green"], in the order the values were added.
Two look-alike objects are still two valuesPop out in the code editor (opens in a new tab)JavaScript
const first = { id: 1 };
const second = { id: 1 };
const selected = new Set([first, first, second]);
console.log(selected.size);
console.log(selected.has(first));

Lines 1 and 2 make two separate objects. They have the same field, but each { } makes a new object. Line 3 puts first in twice and second once. The second first is the very same object, so the Set skips it.

Line 4 prints 2. Line 5 prints true, because first is in the Set.

Looping over a Map or Set

STEP THROUGH

To iterate means to go through the items one by one, for example with a for...of loop. Maps and Sets are iterable: for...of, spread (...), and destructuring all work on them.

A Set gives you its values. A Map gives you its entries, and each entry is a small array: [key, value]. So you can unpack each entry right in the loop: for (const [key, value] of map).

Loop over a Map, then make a Set from its keys
Step 0 of 8Ready
Your turn: follow the blue line

Follow the order: the Map's entries, then forEach with the value first, then a Set made from the keys.

Running in
  1. script
Next: line 1
  ["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(", "));
CallStoreChangeResultBlue line: runs next. 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.

You can also turn one kind of collection into another:

  • new Map(Object.entries(obj)) turns an object into a Map. Object.entries gives the object's [key, value] pairs.
  • Object.fromEntries(map) turns a Map back into an object. This works well when the keys are strings.
  • [...set] and [...map.keys()] make arrays. Then you can use array methods like map and filter.
From an object to a Map and backPop out in the code editor (opens in a new tab)JavaScript
const profile = { name: "Asha", role: "editor" };
const entries = new Map(Object.entries(profile));
console.log(entries.get("role"));
console.log(Object.fromEntries(entries));

Line 1 is a plain object. Line 2 passes its pairs to new Map. Line 3 looks up "role" and prints editor. Line 4 turns the Map back into an object with Object.fromEntries, so it prints the object { name: "Asha", role: "editor" }.

Watch the order in forEach

A Map's forEach method calls your function once for each entry. It passes the value first and the key second: (value, key). In a for...of loop, each entry is [key, value], key first. The two look alike, so say the order out loud while you learn it.

Get it or add it: getOrInsert

INTERACTIVE

A common job is: "Give me the value for this key. If there is none yet, add one first." This is called an upsert, a mix of the words "update" and "insert".

Two new Map methods do this job: Map.prototype.getOrInsert and Map.prototype.getOrInsertComputed. They were added to JavaScript in 2026, and not every browser has them yet. So after the page loads, it checks whether your browser has them. Checking first like this is called feature detection. If the methods are missing, the lesson uses a small helper function that does the same thing. Node 22 also needs the helper.

Does your browser have getOrInsert? Then compare two word counters
Count with the getOrInsertComputed helperPop 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]);
What your browser has, and what the helper didhelper
browser has getOrInsertchecking
browser has getOrInsertComputedchecking
helper entries[["theme","dark"],["letters",7]]
keys the callback ran for["letters"]
Try it yourself

The page checks your browser only after it has loaded. The server that builds the page can't know which browser you use.

getOrInsert and getOrInsertComputed are new in JavaScript, and browsers are still adding them. This lesson does not expect them to be there.

Here is what each one does:

  • getOrInsert(map, key, value): if key is missing, it stores value. Then it returns map.get(key).
  • getOrInsertComputed(map, key, callback): if key is missing, it calls callback(key) and stores what the callback returns. A callback is a function you hand over so it can be called later. Then it returns map.get(key).

If the key is already there, both return the stored value. Nothing is replaced, and the callback is not called.

The real methods are called on the Map itself, like map.getOrInsert(key, value). The helpers below take the Map as their first input instead.

Helpers this page uses when your browser doesn't have getOrInsertPop 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

Lines 1 to 9 are the two helpers. Lines 12 and 13 use the same key, "theme". Line 12 finds no "theme", so it stores "dark". Line 13 finds "dark" already there, so "light" is not used.

Line 14 finds no "letters" key, so it calls the callback. The callback returns 7, the length of the word "letters". Line 15 prints [["theme", "dark"], ["letters", 7]].

Left: the first getOrInsert call finds no theme in the cache, so "dark" goes in and is returned. Right: the second call finds theme is already "dark", so "light" is not used and "dark" is returned.1st call: no theme yet"dark"theme: "dark"cachestored2nd call: theme is there"light"not usedtheme: "dark"cachekept
Line 12 finds no "theme" key, so "dark" goes in. Line 13 finds the key already there, so "light" is not used. Both calls return "dark".

The last line of the code is a note from this page. It ran the helpers on a Map that held score with the value 10. same=10 means the old value was kept. added=5 is the length of the new key, "bonus".

Where you’ll use this

INTERACTIVE

Maps and Sets are everyday tools for getting data ready to show on a page. Some examples:

  • A search page keeps a Set of the filter IDs the user has picked.
  • A stats panel counts events in a Map.
  • A component library keeps measured sizes in a Map, with each page element (an object) as the key. Saving results so you don't have to work them out again is called a cache.
  • A form turns a Map into an object before it saves it.
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 different
map3
set1
Try it yourself

The Map has 2 different words as keys. A word you type again adds one to its count. It does not add a new entry.

The words are listed in the order each one first appeared. That keeps the report easy to read.
A small word counterPop out in the code editor (opens in a new tab)JavaScript
const counts = new Map();
for (const word of ["tea", "tea", "cake"]) {
  counts.set(word, (counts.get(word) ?? 0) + 1);
}
console.log([...counts]);

For each word, line 3 reads the current count. ?? 0 means "use 0 if there is no count yet". Then it saves the count plus one. Line 5 prints [["tea", 2], ["cake", 1]], in the order the words first appeared.

  • Word counters: a Map from each word to its count. It keeps the order the words first appeared.
  • Removing repeats: make a Set from an array, then spread it back into an array.
  • Caches with object keys: a Map keeps every key and value in memory until you delete them. The later WeakMap & WeakSet lesson shows a kind of Map that lets them go.
  • Saving as JSON: turn the Map into an array or an object first. The JSON lesson explains why a Map can't be saved directly.

Map, Set, or plain object?

SORT

To pick one, ask which question your data answers:

  • "What named fields describe this one thing?" Use a plain object.
  • "What value goes with this key?" Use a Map.
  • "Have I seen this value before?" Use a Set.

Sort the cases below.

Choose the collection
  • A user record with fixed fields, saved as JSON
  • Remember data for each page element or other object
  • Keep a list of tags with no repeats
  • Count words as word → count pairs
  • App settings loaded and saved as JSON
  • Remember which product IDs you have already seen
Try it yourself
0 of 6 correct

Put each case under the collection that fits it best.

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

Common misconceptions

  • "A Map is just a fancier object." Map entries are not properties. Keys that are not strings stay as they are, and Object.keys(map) does not list the entries.
  • "A Set compares objects by what is inside them." It checks whether it is the very same object. Two {} objects are two different values.
  • "A Set sorts its values." A Set removes repeats, but it keeps the order you added things. It never sorts.
  • "JSON.stringify(new Map()) saves the entries." It does not: it gives "{}". Turn the Map into an array first, or use Object.fromEntries when the keys are strings.
  • "getOrInsertComputed always runs the callback." It runs the callback only when the key is missing. That is the reason to use it: the work happens only once.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upRemove repeats with a Set

What does this code print?

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 names with a Map

    Run the counter in your head. What are the two printed 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 · PracticeTurn a Map into an object

      What exact JSON text 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 these pairs are added.

        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 · ChallengeBuild a cache with getOrInsertComputed

          Use what the helper does to predict the two printed 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 is new Map([[1, "a"], ["1", "b"]]).size equal to 2?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does this Set with two 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 7In what order does map.forEach pass the key and the value to your function?

              Choose an answer to see the explanation.

            4. Question 4 of 7What does this Set of objects 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 one turns a Map with string keys into a plain object?

              Choose an answer to see the explanation.

            Key takeaways

            • Use a Map for key-value pairs that you add and change, especially when the keys are not strings.
            • Use a Set to keep each value once and to ask "is it in here?" Spread it into an array when you need one.
            • Maps and Sets both keep the order you added things, and both work with for...of.
            • getOrInsert returns the stored value, or adds the value when the key is missing. getOrInsertComputed calls its callback only when the key is missing.
            • Turn a Map into an object or an array on purpose before you save it as JSON.

            One-liner: a Map answers "what value goes with this key?", a Set answers "have I seen this value?", and an object answers "what named fields describe this one thing?"

            Up next: Set operations.

            Back to the Working with data stage.

            CompleteFrontend Clear concepts. Working examples.