JavaScript ยท Lesson 16

JavaScript Maps: Key-Value Pairs ยท Map Methods ยท WeakMap ยท Full Reference

6 phases  ยท  Build: ๐Ÿ”ต Stage 1 ยท Book Catalogue

๐Ÿ‘‹ Welcome to Lesson 16

Work through each phase in order. Complete the task before unlocking the next. Your Build It project unlocks when all phases are done.

๐Ÿ“š 6 phases๐Ÿ—๏ธ ๐Ÿ”ต Stage 1 ยท Book Catalogue๐ŸŒ GitHub Pages
Phase 1 of 6
1. Background: Why Maps Exist

You already know that JavaScript objects store key-value pairs:

javascript
const scores = {
  Alice: 88,
  Bob:   92,
  Carol: 75
};
console.log(scores["Alice"]); // 88

So why does JavaScript need Map when objects already do this job?

Because objects have serious limitations as key-value stores that become painful in real applications:

The 5 Problems with Objects as Key-Value Stores

ProblemObjectMap
Key typesStrings and Symbols onlyAny type ยท objects, arrays, numbers, functions
Key orderNot guaranteed for non-string keysAlways insertion order
SizeNo built-in count ยท must use Object.keys(obj).length.size property instantly
IterationNot directly iterable (need for...in or Object.entries)Directly iterable with for...of
Prototype pollutionInherits keys like toString, constructor from prototypeClean ยท no inherited keys

A Problem Objects Cannot Solve

Imagine you need to store extra data about DOM elements or objects ยท using the object itself as the key:

javascript
// โŒ Objects can only use STRINGS as keys
const visits = {};
const userObj = { name: "Alice" };

visits[userObj] = 5;               // JavaScript converts key to "[object Object]"!
console.log(Object.keys(visits));  // ["[object Object]"]  โ† useless!

// โœ… Map uses the ACTUAL object reference as a key
const visitsMap = new Map();
visitsMap.set(userObj, 5);
console.log(visitsMap.get(userObj)); // 5  โ† works perfectly!

๐Ÿข REAL WORLD: Maps are used for: caching computation results keyed by their input object, counting word frequencies, storing metadata about DOM elements, building lookup tables where keys are not strings, and anywhere you need a reliable ordered key-value structure.



โœ๏ธ Your Task
Practise what you just learned about 1. Background: Why Maps Exist. Open your editor, type the examples above by hand, modify them, and observe what changes.
Phase 2 of 6
2. Topic 1 ยท Maps: Basics & Creation

Phase 1 ยท Conceptual Understanding

A Map is a collection of key-value pairs where:

  • Each key is unique (like Set values ยท no duplicates)
  • Keys can be any JavaScript value (objects, functions, primitives)
  • Pairs are stored and iterated in insertion order
  • Size is always available via .size

Think of a Map like a dictionary ยท each word (key) has exactly one definition (value). But unlike a regular JavaScript object dictionary, your "words" can be anything ยท not just strings.


Creating a Map

Empty Map, then add entries with set():

javascript
const map = new Map();

map.set("name",  "Alice");
map.set("age",   30);
map.set("city",  "Lagos");

console.log(map);
// Map(3) { "name" => "Alice", "age" => 30, "city" => "Lagos" }

console.log(map.size); // 3

โ–ถ Expected Output:

code
Map(3) { "name" => "Alice", "age" => 30, "city" => "Lagos" }
3

From an Array of [key, value] Pairs (Most Common):

Pass an array of two-element arrays to the Map constructor:

javascript
const fruits = new Map([
  ["apple",  5],
  ["banana", 8],
  ["mango",  3]
]);

console.log(fruits);
// Map(3) { "apple" => 5, "banana" => 8, "mango" => 3 }

console.log(fruits.get("banana")); // 8
console.log(fruits.size);          // 3

โ–ถ Expected Output:

code
Map(3) { "apple" => 5, "banana" => 8, "mango" => 3 }
8
3

๐Ÿ’ก TIP: The [key, value] array format is called an entry. You will see this pattern constantly when converting between Maps and arrays. Object.entries(obj) also produces this same format ยท making it easy to convert objects to Maps.


From an Object (via Object.entries):

javascript
const obj = { name: "Bob", score: 92, city: "Accra" };

// Convert object to Map
const map = new Map(Object.entries(obj));

console.log(map);
// Map(3) { "name" => "Bob", "score" => 92, "city" => "Accra" }

Keys Can Be ANY Type ยท The Superpower of Map

This is the single biggest advantage of Map over a plain object:

javascript
const map = new Map();

// String keys (like objects)
map.set("name", "Alice");

// Number keys
map.set(42, "The answer");

// Boolean keys
map.set(true,  "yes");
map.set(false, "no");

// Object as a key!
const user = { id: 1 };
map.set(user, "User profile data");

// Array as a key!
const coords = [10, 20];
map.set(coords, "Location marker");

// Function as a key!
const fn = () => "hello";
map.set(fn, "This function's metadata");

console.log(map.get("name"));   // "Alice"
console.log(map.get(42));       // "The answer"
console.log(map.get(true));     // "yes"
console.log(map.get(user));     // "User profile data"
console.log(map.get(coords));   // "Location marker"
console.log(map.size);          // 6

โ–ถ Expected Output:

code
Alice
The answer
yes
User profile data
Location marker
6

โš ๏ธ WATCH OUT: Like Set, Map uses reference equality for object and array keys. Two objects with the same content are treated as different keys if they are not the same reference in memory.

javascript
const map = new Map();
map.set({ id: 1 }, "first");
map.set({ id: 1 }, "second"); // DIFFERENT key โ€” different object reference!
console.log(map.size); // 2 (not 1!)

size Property ยท Instant Count

javascript
const map = new Map([["a", 1], ["b", 2], ["c", 3]]);
console.log(map.size); // 3

๐Ÿ’ก TIP: Unlike objects where you need Object.keys(obj).length, Maps give you size instantly with .size. For large Maps, this is much faster because the count is maintained internally.


Map Preserves Insertion Order

javascript
const map = new Map();
map.set("z", 26);
map.set("a",  1);
map.set("m", 13);

for (const [key, val] of map) {
  console.log(key, "โ†’", val);
}

โ–ถ Expected Output:

code
z โ†’ 26
a โ†’ 1
m โ†’ 13

Notice the output is in insertion order (z, a, m) ยท NOT alphabetical order. This is guaranteed for Maps.

๐Ÿข REAL WORLD: This matters when you need to show items in the order a user added them ยท like a shopping cart, a history log, or a priority queue.


Iterating a Map

Maps are directly iterable. You can loop over them in several ways:

for...of with destructuring (most common):

javascript
const scores = new Map([
  ["Alice", 88],
  ["Bob",   92],
  ["Carol", 75]
]);

for (const [name, score] of scores) {
  console.log(name + ": " + score);
}

โ–ถ Expected Output:

code
Alice: 88
Bob: 92
Carol: 75

forEach(value, key, map):

javascript
scores.forEach((score, name) => {
  console.log(name + ": " + score);
});

โš ๏ธ WATCH OUT: In Map's forEach, the callback receives (value, key, map) ยท value first, then key. This is the opposite of what many beginners expect. It mirrors Array.forEach(element, index) where the "important" thing comes first.


Converting Map โ†” Array โ†” Object

These conversions are essential in real-world code:

javascript
const map = new Map([["a", 1], ["b", 2], ["c", 3]]);

// Map โ†’ Array of [key, value] pairs
const entries = [...map];
console.log(entries); // [["a",1], ["b",2], ["c",3]]

// Map โ†’ Array of keys only
const keys = [...map.keys()];
console.log(keys); // ["a", "b", "c"]

// Map โ†’ Array of values only
const values = [...map.values()];
console.log(values); // [1, 2, 3]

// Map โ†’ Plain Object (keys must be strings/symbols)
const obj = Object.fromEntries(map);
console.log(obj); // { a: 1, b: 2, c: 3 }

// Plain Object โ†’ Map
const backToMap = new Map(Object.entries(obj));
console.log(backToMap.size); // 3

โ–ถ Expected Output:

code
[["a",1], ["b",2], ["c",3]]
["a", "b", "c"]
[1, 2, 3]
{ a: 1, b: 2, c: 3 }
3

๐Ÿข REAL WORLD: API responses arrive as plain objects (JSON.parse). You convert them to Maps for efficient lookups, process them, then convert back to objects/JSON for sending responses. Object.entries() and Object.fromEntries() are the bridge.


Map vs Object ยท When to Use Which?

SituationUse
Keys are always strings, structure is fixedObject ยท simpler syntax
Keys are non-strings (objects, numbers, etc.)Map
Need guaranteed insertion orderMap
Need to know the count quicklyMap (.size)
Frequently adding and removing entriesMap ยท better performance
Need to serialise to JSON directlyObject ยท JSON.stringify doesn't support Map
Need set operations (union etc.)Set, not Map
Passing data to external APIsObject (most APIs expect objects)


โœ๏ธ Your Task
Practise what you just learned about 2. Topic 1 ยท Maps: Basics & Creation. Open your editor, type the examples above by hand, modify them, and observe what changes.
Phase 3 of 6
3. Topic 2 ยท Map Methods

Phase 1 ยท Conceptual Understanding

Maps have a clean, focused API. Every method has one clear job.


set(key, value) ยท Add or Update an Entry

Adds a new key-value pair. If the key already exists, its value is updated. Returns the Map itself (enabling chaining).

javascript
const map = new Map();

// Add new entries
map.set("name", "Alice");
map.set("age",  30);

console.log(map); // Map(2) { "name" => "Alice", "age" => 30 }

// Update existing entry
map.set("age", 31); // "age" key already exists โ†’ updates value

console.log(map); // Map(2) { "name" => "Alice", "age" => 31 }
console.log(map.size); // Still 2 โ€” no new entry created

โ–ถ Expected Output:

code
Map(2) { "name" => "Alice", "age" => 30 }
Map(2) { "name" => "Alice", "age" => 31 }
2

Chaining set() calls:

javascript
const config = new Map()
  .set("host",    "localhost")
  .set("port",    3000)
  .set("debug",   true)
  .set("timeout", 5000);

console.log(config.size); // 4

๐Ÿข REAL WORLD: Building a configuration Map by chaining .set() calls is a common pattern in server-side Node.js code.


get(key) ยท Retrieve a Value

Returns the value for a given key. Returns undefined if the key doesn't exist.

javascript
const map = new Map([
  ["city",    "Nairobi"],
  ["country", "Kenya"],
  ["pop",     5_000_000]
]);

console.log(map.get("city"));      // "Nairobi"
console.log(map.get("country"));   // "Kenya"
console.log(map.get("pop"));       // 5000000
console.log(map.get("language"));  // undefined  โ† key doesn't exist

โ–ถ Expected Output:

code
Nairobi
Kenya
5000000
undefined

๐Ÿ› COMMON MISTAKE: Always check with has() before using get() if the value could legitimately be undefined or 0 (falsy values). Do NOT use if (map.get(key)) ยท it fails for falsy values!

javascript
// โŒ WRONG โ€” fails when value is 0, false, "", null, undefined
if (map.get("score")) { ... }

// โœ… CORRECT โ€” checks existence, not truthiness
if (map.has("score")) { ... }

has(key) ยท Check if a Key Exists

Returns true if the key exists in the Map, false otherwise.

javascript
const map = new Map([
  ["apple", 5],
  ["banana", 0], // โ† value is 0, which is falsy!
]);

// โœ… Correct existence check
console.log(map.has("apple"));   // true
console.log(map.has("banana"));  // true  โ† correct! (value is 0 but key exists)
console.log(map.has("mango"));   // false

// โŒ Wrong approach โ€” misses falsy values
console.log(!!map.get("banana")); // false โ† WRONG! key exists but value is falsy

โ–ถ Expected Output:

code
true
true
false
false

delete(key) ยท Remove an Entry

Removes the key-value pair for the given key. Returns true if the key existed and was removed, false if not found.

javascript
const map = new Map([
  ["a", 1],
  ["b", 2],
  ["c", 3]
]);

const removed = map.delete("b");
console.log(removed);   // true
console.log(map);       // Map(2) { "a" => 1, "c" => 3 }
console.log(map.size);  // 2

const notFound = map.delete("z");
console.log(notFound);  // false

โ–ถ Expected Output:

code
true
Map(2) { "a" => 1, "c" => 3 }
2
false

clear() ยท Remove All Entries

Empties the Map completely. The Map object still exists but is now empty.

javascript
const map = new Map([["a", 1], ["b", 2], ["c", 3]]);
console.log(map.size); // 3

map.clear();

console.log(map.size); // 0
console.log(map);      // Map(0) {}

keys() ยท Iterator of All Keys

Returns an iterator yielding each key in insertion order.

javascript
const map = new Map([
  ["name",  "Alice"],
  ["age",   30],
  ["city",  "Lagos"]
]);

for (const key of map.keys()) {
  console.log(key);
}
// name
// age
// city

// Convert to array
const keysArr = [...map.keys()];
console.log(keysArr); // ["name", "age", "city"]

โ–ถ Expected Output:

code
name
age
city
["name", "age", "city"]

values() ยท Iterator of All Values

Returns an iterator yielding each value in insertion order.

javascript
const scores = new Map([
  ["Alice", 88],
  ["Bob",   92],
  ["Carol", 75]
]);

for (const score of scores.values()) {
  console.log(score);
}
// 88
// 92
// 75

// Useful: calculate average using spread
const avg = [...scores.values()].reduce((s, v) => s + v, 0) / scores.size;
console.log("Average:", avg.toFixed(1)); // 85.0

โ–ถ Expected Output:

code
88
92
75
Average: 85.0

entries() ยท Iterator of [key, value] Pairs

Returns an iterator yielding [key, value] arrays. This is the default iteration behaviour of a Map ยท for...of map is the same as for...of map.entries().

javascript
const map = new Map([
  ["x", 10],
  ["y", 20],
  ["z", 30]
]);

for (const [key, value] of map.entries()) {
  console.log(key + " โ†’ " + value);
}
// x โ†’ 10
// y โ†’ 20
// z โ†’ 30

// These are identical:
for (const [k, v] of map)         { /* same */ }
for (const [k, v] of map.entries()) { /* same */ }

forEach(callback) ยท Run a Function for Each Entry

Calls callback(value, key, map) for each entry in insertion order.

javascript
const prices = new Map([
  ["apple",  1.20],
  ["banana", 0.50],
  ["mango",  2.00]
]);

let total = 0;
prices.forEach((price, fruit) => {
  console.log(fruit + ": $" + price.toFixed(2));
  total += price;
});
console.log("Total: $" + total.toFixed(2));

โ–ถ Expected Output:

code
apple: $1.20
banana: $0.50
mango: $2.00
Total: $3.70

๐Ÿค” THINK ABOUT IT: In forEach, why does value come before key? The Map is designed this way so code reading left-to-right makes intuitive sense: "for each entry, here is its value and here is its key". Also, it is consistent with Array.forEach(element, index) where the "primary data" comes first.


groupBy() ยท Group Array Items into a Map (Static Method)

Map.groupBy(iterable, keyFn) groups the elements of an iterable into a Map, where each key is the result of calling keyFn on each element and the value is an array of elements sharing that key.

javascript
const students = [
  { name: "Alice",  grade: "A", score: 92 },
  { name: "Bob",    grade: "B", score: 78 },
  { name: "Carol",  grade: "A", score: 95 },
  { name: "David",  grade: "C", score: 65 },
  { name: "Eve",    grade: "B", score: 81 },
];

// Group students by their grade
const byGrade = Map.groupBy(students, s => s.grade);

console.log(byGrade.get("A"));
// [{ name: "Alice", grade: "A", score: 92 },
//  { name: "Carol", grade: "A", score: 95 }]

console.log(byGrade.get("B"));
// [{ name: "Bob", grade: "B", score: 78 },
//  { name: "Eve", grade: "B", score: 81 }]

// Iterate grouped results
for (const [grade, group] of byGrade) {
  const names = group.map(s => s.name).join(", ");
  console.log(`Grade ${grade}: ${names}`);
}

โ–ถ Expected Output:

code
[{ name: "Alice", ... }, { name: "Carol", ... }]
[{ name: "Bob", ... }, { name: "Eve", ... }]
Grade A: Alice, Carol
Grade B: Bob, Eve
Grade C: David

๐Ÿข REAL WORLD: Map.groupBy() replaces the classic reduce grouping pattern that every developer had to write manually. Use it to group products by category, transactions by date, users by role ยท any "bucket" grouping task.

โš ๏ธ WATCH OUT: Map.groupBy() is a relatively new static method (ES2024). For older environments, the manual equivalent with reduce is:

javascript
const grouped = students.reduce((map, s) => {
const key = s.grade;
if (!map.has(key)) map.set(key, []);
map.get(key).push(s);
return map;
}, new Map());

Summary: All Map Methods

MethodReturnsDescription
new Map(entries?)MapCreate from [[k,v],...] or empty
map.set(key, val)MapAdd/update entry (chainable)
map.get(key)Value or undefinedRetrieve by key
map.has(key)BooleanCheck key existence
map.delete(key)BooleanRemove entry
map.clear()undefinedRemove all entries
map.sizeNumberCount of entries
map.keys()MapIteratorIterate keys
map.values()MapIteratorIterate values
map.entries()MapIteratorIterate [key, value] pairs
map.forEach(fn)undefinedRun fn(value, key, map)
Map.groupBy(iter, fn)MapGroup iterable by key function


โœ๏ธ Your Task
> โš ๏ธ WATCH OUT: Map.groupBy() is a relatively new static method (ES2024). For older environments, the manual equivalent with reduce is: > > `javascript > const grouped = students.reduce((map, s) => { > const key = s.grade; > if (!map.has(key)) map.set(key, []); > map.get(key).push(s); > return map; > }, new Map()); > ` ยท
Phase 4 of 6
4. Topic 3 ยท WeakMap

Phase 1 ยท Conceptual Understanding

WeakMap is to Map what WeakSet is to Set ยท a special memory-friendly variant with strict rules and intentional limitations.

The Core Idea ยท Memory Without Ownership

A regular Map holds a strong reference to its keys ยท this prevents the garbage collector from cleaning up the key objects as long as the Map exists. A WeakMap holds weak references to its keys ยท meaning if nothing else in the program holds a reference to a key object, the garbage collector can reclaim it and its entry is automatically removed from the WeakMap.

code
Regular Map:
  Object โ†โ”€โ”€โ”€โ”€ STRONG key reference โ”€โ”€โ”€โ”€ Map
  (Object CANNOT be collected while Map exists)

WeakMap:
  Object โ†โ”€โ”€โ”€โ”€ WEAK key reference  โ”€โ”€โ”€โ”€ WeakMap
  (Object CAN be collected โ€” WeakMap doesn't prevent it)

WeakMap Rules ยท Two Critical Constraints

  1. Keys MUST be objects (not primitives like numbers, strings, booleans)
  2. Not iterable ยท no for...of, no keys(), no values(), no entries(), no size, no clear()

These constraints exist by design ยท because items can disappear at any time (garbage collected), a predictable iteration or count would be meaningless.


Creating a WeakMap

javascript
const wm = new WeakMap();

const key1 = { id: 1 };
const key2 = { id: 2 };
const key3 = { id: 3 };

wm.set(key1, "Data for object 1");
wm.set(key2, "Data for object 2");
wm.set(key3, "Data for object 3");

console.log(wm.get(key1)); // "Data for object 1"
console.log(wm.has(key2)); // true

โ–ถ Expected Output:

code
Data for object 1
true

WeakMap Keys MUST Be Objects

javascript
const wm = new WeakMap();

// โŒ Primitives are NOT allowed as keys
try {
  wm.set("hello", "value"); // TypeError!
} catch (e) {
  console.log("Error:", e.message);
  // "Invalid value used as weak map key"
}

try {
  wm.set(42, "value");      // TypeError!
} catch (e) {
  console.log("Error:", e.message);
}

// โœ… Only object keys work
const obj = { name: "test" };
wm.set(obj, "this works");
console.log(wm.get(obj)); // "this works"

WeakMap Methods ยท Only Four

javascript
const wm  = new WeakMap();
const key = { id: 1 };

wm.set(key, "some value");   // Add/update entry
console.log(wm.get(key));    // "some value"
console.log(wm.has(key));    // true
wm.delete(key);              // Remove entry
console.log(wm.has(key));    // false
MethodReturnsDescription
wm.set(objKey, value)WeakMapAdd/update entry (key must be object)
wm.get(objKey)Value or undefinedRetrieve value by object key
wm.has(objKey)BooleanCheck if key exists
wm.delete(objKey)BooleanRemove entry

โš ๏ธ WATCH OUT: There is no wm.size, wm.clear(), wm.keys(), wm.values(), wm.entries(), or wm.forEach(). WeakMap intentionally cannot be iterated ยท items may vanish at any time due to garbage collection.


Automatic Memory Cleanup ยท The Key Benefit

javascript
let user = { name: "Alice", id: 1 };
const cache = new WeakMap();

cache.set(user, { preferences: { theme: "dark" } });

console.log(cache.has(user)); // true

// When we remove our reference to the user object...
user = null;

// JavaScript's garbage collector will eventually:
// 1. See that no strong references to the original { name: "Alice" } object remain
// 2. Clean up that object from memory
// 3. Automatically remove its entry from the WeakMap too
// โ†’ No memory leak!

Real-World Use Case 1 ยท Private Object Data

Before JavaScript had private class fields (#), WeakMap was the standard way to store private data for class instances:

javascript
// Private storage โ€” only accessible via the API, not directly on the object
const _private = new WeakMap();

class BankAccount {
  constructor(owner, balance) {
    // Store private data in WeakMap keyed by this instance
    _private.set(this, { owner, balance, transactions: [] });
  }

  deposit(amount) {
    const data = _private.get(this);
    data.balance += amount;
    data.transactions.push({ type: "deposit", amount });
    console.log(`Deposited $${amount}. New balance: $${data.balance}`);
  }

  getBalance() {
    return _private.get(this).balance;
  }

  getOwner() {
    return _private.get(this).owner;
  }
}

const account = new BankAccount("Alice", 1000);
account.deposit(500);
console.log("Balance:", account.getBalance());
console.log("Owner:", account.getOwner());

// โ† Cannot access _private directly from outside this module!
// When 'account' goes out of scope, WeakMap cleans up automatically

โ–ถ Expected Output:

code
Deposited $500. New balance: $1500
Balance: 1500
Owner: Alice

Real-World Use Case 2 ยท Caching Computed Results Per Object

javascript
const computeCache = new WeakMap();

function getExpensiveData(obj) {
  // Return cached result if already computed for this object
  if (computeCache.has(obj)) {
    console.log("Cache hit!");
    return computeCache.get(obj);
  }

  // Simulate expensive computation
  console.log("Computing...");
  const result = { processed: true, data: obj.value * 2 };

  // Cache it โ€” but without preventing obj from being garbage collected
  computeCache.set(obj, result);
  return result;
}

const record = { value: 21 };

console.log(getExpensiveData(record)); // Computing...  { processed: true, data: 42 }
console.log(getExpensiveData(record)); // Cache hit!    { processed: true, data: 42 }

// When record goes out of scope, cache entry is automatically cleaned up

โ–ถ Expected Output:

code
Computing...
{ processed: true, data: 42 }
Cache hit!
{ processed: true, data: 42 }

Real-World Use Case 3 ยท DOM Element Metadata

javascript
const elementData = new WeakMap();

function attachData(element, data) {
  elementData.set(element, data);
}

function getData(element) {
  return elementData.get(element) || null;
}

// In a browser:
// const btn = document.querySelector("#submitBtn");
// attachData(btn, { clicks: 0, lastClicked: null });
//
// When btn is removed from the DOM and no JS holds a reference,
// WeakMap automatically releases its entry โ€” no cleanup code needed!

๐Ÿข REAL WORLD: JavaScript frameworks like Vue.js use WeakMap internally to store reactive metadata about component objects. When a component is destroyed, its metadata is automatically garbage collected ยท no manual cleanup required.


Map vs WeakMap ยท Full Comparison

FeatureMapWeakMap
Key typesAny valueObjects ONLY
Value typesAny valueAny value
Key referencesStrongWeak (allows GC)
size propertyโœ… YesโŒ No
clear()โœ… YesโŒ No
Iterableโœ… YesโŒ No
keys() / values() / entries()โœ… YesโŒ No
forEach()โœ… YesโŒ No
set() / get() / has() / delete()โœ… Yesโœ… Yes
Best useGeneral key-value storagePrivate data, caches, DOM metadata


โœ๏ธ Your Task
Practise what you just learned about 4. Topic 3 ยท WeakMap. Open your editor, type the examples above by hand, modify them, and observe what changes.
Phase 5 of 6
5. Topic 4 ยท Complete Map Reference

Quick reference for every Map and WeakMap feature.


Map ยท Constructor

javascript
new Map()                      // empty Map
new Map([[k1,v1], [k2,v2]])    // from entries array
new Map(Object.entries(obj))   // from plain object
new Map(anotherMap)            // copy of another Map

Map ยท Properties

PropertyTypeDescription
map.sizeNumberCount of key-value entries
map[Symbol.iterator]FunctionMakes Map iterable (same as entries())

Map ยท Instance Methods

MethodReturnsDescription
map.set(key, value)MapAdd/update entry; chainable
map.get(key)Value or undefinedGet value by key
map.has(key)BooleanTrue if key exists
map.delete(key)BooleanRemove entry; true if found
map.clear()undefinedRemove all entries
map.keys()MapIteratorIterate keys in insertion order
map.values()MapIteratorIterate values in insertion order
map.entries()MapIteratorIterate [key, value] pairs
map.forEach(fn)undefinedCall fn(value, key, map) for each

Map ยท Static Methods

MethodReturnsDescription
Map.groupBy(iterable, keyFn)MapGroup elements by key function result

Map ยท Conversion Cheat Sheet

javascript
const map = new Map([["a", 1], ["b", 2], ["c", 3]]);

// โ†’ Array of entries [[k,v], ...]
[...map]                   // [["a",1],["b",2],["c",3]]
[...map.entries()]         // same

// โ†’ Array of keys
[...map.keys()]            // ["a","b","c"]

// โ†’ Array of values
[...map.values()]          // [1, 2, 3]

// โ†’ Plain object
Object.fromEntries(map)    // { a:1, b:2, c:3 }

// โ† From plain object
new Map(Object.entries({ a:1, b:2 }))

// โ† From array
new Map([["a",1],["b",2]])

// โ† From another Map (shallow copy)
new Map(existingMap)

WeakMap ยท Constructor

javascript
new WeakMap()                         // empty
new WeakMap([[objKey, value], ...])   // from entries (keys must be objects)

WeakMap ยท Instance Methods (Only Four)

MethodReturnsDescription
wm.set(objKey, value)WeakMapAdd/update entry (key must be object)
wm.get(objKey)Value or undefinedGet value by object key
wm.has(objKey)BooleanTrue if key exists
wm.delete(objKey)BooleanRemove entry

Choosing the Right Structure ยท Decision Guide

code
Do you need key-value pairs?
  โ”œโ”€ YES โ†’
  โ”‚     Are keys always strings?
  โ”‚       โ”œโ”€ YES, simple fixed structure โ†’ Plain Object {}
  โ”‚       โ””โ”€ NO (objects, numbers, etc. as keys) or need order/size โ†’ MAP
  โ”‚
  โ”‚     Do keys need to be garbage collected?
  โ”‚       โ””โ”€ YES (DOM nodes, class instances) โ†’ WEAKMAP
  โ”‚
  โ””โ”€ NO โ†’
        Do you need unique values (not pairs)?
          โ”œโ”€ YES โ†’ SET
          โ””โ”€ YES + auto GC โ†’ WEAKSET


โœ๏ธ Your Task
Practise what you just learned about 5. Topic 4 ยท Complete Map Reference. Open your editor, type the examples above by hand, modify them, and observe what changes.
Phase 6 of 6
6. Applied Exercises

Phase 2 ยท Applied Exercises


Exercise 1 ยท Map Builder & Reader ๐Ÿ—๏ธ

Objective: Practice creating Maps, using set, get, has, delete, and iterating.

Scenario: You are building a student contact directory for a school. Each student ID maps to a contact record object.

Warm-up Micro-Demo:

javascript
const dir = new Map();
dir.set("S001", { name: "Amara", phone: "080-1234-5678" });
dir.set("S002", { name: "Kwame", phone: "081-9876-5432" });

console.log(dir.get("S001").name); // "Amara"
console.log(dir.size);             // 2

โ–ถ Expected Output:

code
Amara
2

Task A ยท Build the Directory

javascript
const directory = new Map([
  ["S001", { name: "Amara Diallo",    phone: "080-111-2222", grade: "A" }],
  ["S002", { name: "Kwame Asante",    phone: "081-333-4444", grade: "B" }],
  ["S003", { name: "Fatima Rashid",   phone: "082-555-6666", grade: "A" }],
  ["S004", { name: "Emeka Obi",       phone: "083-777-8888", grade: "C" }],
  ["S005", { name: "Priya Sharma",    phone: "084-999-0000", grade: "B" }],
]);

// 1. Look up a student
const student = directory.get("S003");
console.log("Found:", student.name, "โ€”", student.grade);

// 2. Check existence before updating
const updateId = "S002";
if (directory.has(updateId)) {
  const current = directory.get(updateId);
  directory.set(updateId, { ...current, grade: "A" }); // promote grade
  console.log("Updated:", directory.get(updateId).name, "โ†’ grade A");
}

// 3. Add a new student
directory.set("S006", { name: "Omar Hassan", phone: "085-123-4567", grade: "B" });
console.log("Directory size:", directory.size);

// 4. Remove a student
directory.delete("S004");
console.log("After removal:", directory.size);

// 5. Print all A-grade students
console.log("\nA-grade students:");
for (const [id, info] of directory) {
  if (info.grade === "A") {
    console.log(`  ${id}: ${info.name}`);
  }
}

Expected Output:

code
Found: Fatima Rashid โ€” A
Updated: Kwame Asante โ†’ grade A
Directory size: 6
After removal: 5

A-grade students:
  S001: Amara Diallo
  S002: Kwame Asante
  S003: Fatima Rashid

Self-check questions:

  • Why use a Map instead of a plain object for this directory?
  • What does directory.get("S999") return and how should you handle it?
  • Why is has() checked before get() when the value could be undefined?

Exercise 2 ยท Word Frequency Counter ๐Ÿ“Š

Objective: Practice using a Map to count occurrences, then sort and display results.

Scenario: You're building a text analysis tool for a content team. Given a block of text, count word frequencies and find the most common words.

Warm-up Micro-Demo:

javascript
const counts = new Map();
const words  = ["apple", "banana", "apple", "cherry", "banana", "apple"];

words.forEach(word => {
  counts.set(word, (counts.get(word) || 0) + 1);
});

console.log(counts.get("apple"));  // 3
console.log(counts.get("banana")); // 2

โ–ถ Expected Output:

code
3
2

Task A ยท Full Word Counter

javascript
function analyseText(text) {
  // Normalise: lowercase, remove punctuation, split into words
  const words = text
    .toLowerCase()
    .replace(/[^a-z\s]/g, "")
    .split(/\s+/)
    .filter(w => w.length > 0);

  // Count with Map
  const freq = new Map();
  for (const word of words) {
    freq.set(word, (freq.get(word) || 0) + 1);
  }

  // Sort by frequency (descending) โ€” convert to array first
  const sorted = [...freq.entries()].sort((a, b) => b[1] - a[1]);

  return { freq, sorted, totalWords: words.length, uniqueWords: freq.size };
}

const text = `JavaScript is a powerful language. JavaScript runs in the browser 
and on the server. The browser renders JavaScript. Learning JavaScript is 
essential for web development. Web development uses JavaScript everywhere.`;

const result = analyseText(text);

console.log("Total words :", result.totalWords);
console.log("Unique words:", result.uniqueWords);
console.log("\nTop 5 words:");
result.sorted.slice(0, 5).forEach(([word, count], i) => {
  const bar = "โ–ˆ".repeat(count);
  console.log(`  ${(i + 1)}. ${word.padEnd(15)} ${bar} (${count})`);
});

console.log("\nFrequency of 'javascript':", result.freq.get("javascript"));

Expected Output:

code
Total words : 36
Unique words: 20

Top 5 words:
  1. javascript     โ–ˆโ–ˆโ–ˆโ–ˆ (5)
  2. is             โ–ˆโ–ˆโ–ˆ (3)
  3. the            โ–ˆโ–ˆโ–ˆ (3)
  4. web            โ–ˆโ–ˆโ–ˆ (3)
  5. development    โ–ˆโ–ˆ (2)

Frequency of 'javascript': 5

Self-check questions:

  • Why is a Map better than a plain object for counting words?
  • Why does counts.get(word) || 0 work for initialising missing keys?
  • How would you filter out common words like "the", "is", "a" (stop words)?

Exercise 3 ยท Grouping with Map.groupBy() ๐Ÿ—‚๏ธ

Objective: Practice Map.groupBy() and manual grouping with reduce.

Scenario: You're building a sales reporting dashboard that groups transactions by region, month, and status.

Warm-up Micro-Demo:

javascript
const items = [
  { name: "Book",   category: "education" },
  { name: "Pen",    category: "education" },
  { name: "Laptop", category: "tech"      },
];

const grouped = Map.groupBy(items, item => item.category);
console.log(grouped.get("education").length); // 2
console.log(grouped.get("tech").length);      // 1

Task A ยท Sales Grouping

javascript
const transactions = [
  { id: 1,  amount: 4500, region: "North", month: "Jan", status: "paid"    },
  { id: 2,  amount: 3200, region: "South", month: "Jan", status: "pending" },
  { id: 3,  amount: 5800, region: "North", month: "Feb", status: "paid"    },
  { id: 4,  amount: 2900, region: "East",  month: "Jan", status: "paid"    },
  { id: 5,  amount: 6100, region: "South", month: "Feb", status: "paid"    },
  { id: 6,  amount: 3750, region: "East",  month: "Feb", status: "pending" },
  { id: 7,  amount: 4100, region: "North", month: "Mar", status: "paid"    },
  { id: 8,  amount: 1800, region: "South", month: "Mar", status: "failed"  },
];

// 1. Group by region
const byRegion = Map.groupBy(transactions, t => t.region);
console.log("=== Sales by Region ===");
for (const [region, txns] of byRegion) {
  const total = txns.reduce((sum, t) => sum + t.amount, 0);
  console.log(`  ${region.padEnd(6)}: ${txns.length} transactions โ€” $${total.toLocaleString()}`);
}

// 2. Group by status
const byStatus = Map.groupBy(transactions, t => t.status);
console.log("\n=== Sales by Status ===");
for (const [status, txns] of byStatus) {
  const total = txns.reduce((sum, t) => sum + t.amount, 0);
  console.log(`  ${status.padEnd(8)}: ${txns.length} txns โ€” $${total.toLocaleString()}`);
}

// 3. Only paid transactions, grouped by month
const paid = transactions.filter(t => t.status === "paid");
const paidByMonth = Map.groupBy(paid, t => t.month);
console.log("\n=== Paid Transactions by Month ===");
for (const [month, txns] of paidByMonth) {
  const total = txns.reduce((sum, t) => sum + t.amount, 0);
  console.log(`  ${month}: $${total.toLocaleString()}`);
}

Expected Output:

code
=== Sales by Region ===
  North : 3 transactions โ€” $14,400
  South : 3 transactions โ€” $11,100
  East  : 2 transactions โ€” $6,650

=== Sales by Status ===
  paid    : 6 txns โ€” $26,550
  pending : 2 txns โ€” $6,950
  failed  : 1 txns โ€” $1,800

=== Paid Transactions by Month ===
  Jan: $7,400
  Feb: $11,900
  Mar: $4,100

Self-check questions:

  • What does Map.groupBy return and how is it different from Array.reduce?
  • Why is the result a Map and not a plain object?
  • How would you find the region with the highest total sales after grouping?


โœ๏ธ Your Task
ยท ### Exercise 1 ยท Map Builder & Reader ๐Ÿ—๏ธ Objective: Practice creating Maps, using set, get, has, delete, and iterating. Scenario: You are building a student contact directory for a school. Each student ID maps to a contact record object.
๐Ÿ—๏ธ Build It โ€” Mini Project
๐Ÿ”ต Stage 1 ยท Book Catalogue

Phase 3 ยท Project Simulation

Real-world scenario: You are building a library book management system. The system needs to:

  • Store books in a Map (ISBN โ†’ book details)
  • Track borrowing status per book
  • Use WeakMap to store private fine/penalty data per borrower object
  • Generate statistics by genre using Map.groupBy()
  • Search, sort, and report on the collection

๐Ÿ”ต Stage 1 ยท Book Catalogue

Goal: Build a Map-based book catalogue with add, search, and update operations.

Simple stage preview:

starter.html
const catalogue = new Map();
catalogue.set("978-0-00-001", { title: "Dune", author: "Frank Herbert", available: true });
console.log(catalogue.get("978-0-00-001").title); // "Dune"

Lesson 16 complete! ๐ŸŽ‰

You covered: