Maps

Intermediate · 16 min read · ▶ live playground · ✦ checkpoint

A plain object is great when you are describing one thing: a player, a settings file, a JSON-shaped response. A Map is for a different job: storing key-value pairs when the collection itself is the tool. Use a Map when you need any value as a key, guaranteed insertion order (entries come back in the order you added their keys), and a built-in size.

Map is a key-value collection

A key-value collection stores values behind keys, so the program can look them up later. Objects already do a version of this with properties:

const objectLookup = {};
objectLookup[1] = "number key";
objectLookup["1"] = "string key";
 
console.log(objectLookup[1]);
console.log(objectLookup["1"]);
 
const mapLookup = new Map();
mapLookup.set(1, "number key");
mapLookup.set("1", "string key");
 
console.log(mapLookup.size);
console.log(mapLookup.get(1));
console.log(mapLookup.get("1"));

string key twice. The object converted both keys into the same property name. The Map keeps 2 entries: number key and string key.

That is the first big difference. In the object example, 1 and "1" collide because object property names are names. In the Map example, 1 and "1" are different keys because a Map can use values directly.

Think of an object as a labeled card: it has fields like name, score, and active. Think of a Map as a coat-check counter: you hand it a real ticket value, and later the exact ticket gets your item back.

Keys can be objects too

Maps get especially useful after the reference model from Section 7. If a key can be any value, it can be an object reference. And object keys follow identity, not shape:

const mina = { name: "Mina" };
const sameShapeMina = { name: "Mina" };
 
const scores = new Map();
scores.set(mina, 18);
 
console.log(scores.get(mina));
console.log(scores.get(sameShapeMina));
console.log(scores.has(sameShapeMina));

18, then undefined, then false.

sameShapeMina looks like mina, but it is a different object in the heap. The Map uses the exact reference ticket as the key. That makes Maps a natural fit for extra data about objects, lists of live objects, saved lookup data, and the Map.groupBy buckets you previewed in arrays.

Get, set, has, delete

The everyday Map methods are small and direct:

  • set(key, value) adds or replaces an entry.
  • get(key) reads the value for a key.
  • has(key) asks whether the key exists.
  • delete(key) removes a key and returns true if something was removed.
  • size is a property, not a method.
const inventory = new Map([
  ["notebook", 3],
  ["pen", 8],
]);
 
inventory.set("pen", 9);
inventory.set("eraser", 0);
 
console.log(inventory.size);
console.log(inventory.get("pen"));
console.log(inventory.has("eraser"));
console.log(inventory.get("tape"));
console.log(inventory.delete("notebook"));
console.log(inventory.size);

3, 9, true, undefined, true, then 2.

You can start a Map from an array of two-item arrays: [key, value]. That shape is called an entry, a key-value pair treated as one item.

Notice the get("tape") result: missing keys read as undefined. If undefined could be a real stored value in your Map, use has(key) to tell "stored but empty" apart from "not here."

Try it — build an ID lookup

Run this, then inspect membersById at the prompt. Try membersById.get("u1").name, membersById.size, and Array.from(membersById.keys()).

javascript — live consolereal engine
⌘/Ctrl + Enter to run

This pattern shows up constantly. Arrays are good for "walk through every member." Maps are good for "jump straight to the member with this ID." You could build an object lookup for string IDs too, but Map makes the collection behavior explicit and gives you size, has, and ordered iteration without extra helper calls.

Iterating entries in order

A Map remembers insertion order. When you loop over it, entries come out in the order their keys were first added. Updating an existing key changes the value but does not move the entry:

const workflow = new Map([
  ["draft", "write the first version"],
  ["review", "find mistakes"],
  ["ship", "publish"],
]);
 
workflow.set("review", "make the lesson sharper");
workflow.set("archive", "save the notes");
 
for (const [stage, task] of workflow) {
  console.log(`${stage}: ${task}`);
}
 
console.log(Array.from(workflow.keys()).join(" -> "));
console.log(Array.from(workflow.values()).join(" | "));

draft, review, ship, and archive print in that order. Then the keys print as draft -> review -> ship -> archive, and the values print in the same order.

The for...of loop receives entries. Destructuring turns each entry into [stage, task], which is the same key-value pair shape you used when you created the Map. If you want only keys or only values, .keys() and .values() give you those items, and Array.from(...) turns them into arrays when an array method or display string would be more convenient.

Plain objects do have property-order rules, but Maps keep the mental model clean: first added, first visited. That makes them pleasant for registries, grouped buckets, lookup tables, and small in-memory caches where order still matters.

WeakMap: metadata that lets go

A WeakMap is a specialized cousin of Map whose keys must be garbage-collectable, meaning the engine can clean them up when nothing else needs them. In everyday code that means objects; modern JavaScript also allows non-registered symbols, a Symbol detail you will learn in lesson 13.2. WeakMap is useful for attaching metadata, extra data about another object, or a cache, saved lookup data, to objects created somewhere else: when the key is no longer used, that extra data can disappear too. Because the entries are weak, a WeakMap has no size and cannot be iterated. For now, read it as "metadata that lets go"; ordinary Maps are the structure you reach for when you need to list, count, and process entries.

Checkpoint

Answer all three to mark this lesson complete

Maps give you ordered key-value lookups. Next, Sets take one step sideways: instead of mapping keys to values, they store unique values by design.

+50 XP on completion