You already know that JavaScript objects store key-value pairs:
const scores = {
Alice: 88,
Bob: 92,
Carol: 75
};
console.log(scores["Alice"]); // 88So 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
| Problem | Object | Map |
|---|---|---|
| Key types | Strings and Symbols only | Any type ยท objects, arrays, numbers, functions |
| Key order | Not guaranteed for non-string keys | Always insertion order |
| Size | No built-in count ยท must use Object.keys(obj).length | .size property instantly |
| Iteration | Not directly iterable (need for...in or Object.entries) | Directly iterable with for...of |
| Prototype pollution | Inherits keys like toString, constructor from prototype | Clean ยท 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:
// โ 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.
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():
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:
Map(3) { "name" => "Alice", "age" => 30, "city" => "Lagos" }
3From an Array of [key, value] Pairs (Most Common):
Pass an array of two-element arrays to the Map constructor:
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:
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):
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:
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:
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.
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
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
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:
z โ 26
a โ 1
m โ 13Notice 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):
const scores = new Map([
["Alice", 88],
["Bob", 92],
["Carol", 75]
]);
for (const [name, score] of scores) {
console.log(name + ": " + score);
}โถ Expected Output:
Alice: 88
Bob: 92
Carol: 75forEach(value, key, map):
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 mirrorsArray.forEach(element, index)where the "important" thing comes first.
Converting Map โ Array โ Object
These conversions are essential in real-world code:
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:
[["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()andObject.fromEntries()are the bridge.
Map vs Object ยท When to Use Which?
| Situation | Use |
|---|---|
| Keys are always strings, structure is fixed | Object ยท simpler syntax |
| Keys are non-strings (objects, numbers, etc.) | Map |
| Need guaranteed insertion order | Map |
| Need to know the count quickly | Map (.size) |
| Frequently adding and removing entries | Map ยท better performance |
| Need to serialise to JSON directly | Object ยท JSON.stringify doesn't support Map |
| Need set operations (union etc.) | Set, not Map |
| Passing data to external APIs | Object (most APIs expect objects) |
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).
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:
Map(2) { "name" => "Alice", "age" => 30 }
Map(2) { "name" => "Alice", "age" => 31 }
2Chaining set() calls:
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.
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:
Nairobi
Kenya
5000000
undefined๐ COMMON MISTAKE: Always check with
has()before usingget()if the value could legitimately beundefinedor0(falsy values). Do NOT useif (map.get(key))ยท it fails for falsy values!// โ 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.
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:
true
true
false
falsedelete(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.
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:
true
Map(2) { "a" => 1, "c" => 3 }
2
falseclear() ยท Remove All Entries
Empties the Map completely. The Map object still exists but is now empty.
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.
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:
name
age
city
["name", "age", "city"]values() ยท Iterator of All Values
Returns an iterator yielding each value in insertion order.
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:
88
92
75
Average: 85.0entries() ยท 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().
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.
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:
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 withArray.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.
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:
[{ 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 classicreducegrouping 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 withreduceis: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
| Method | Returns | Description |
|---|---|---|
new Map(entries?) | Map | Create from [[k,v],...] or empty |
map.set(key, val) | Map | Add/update entry (chainable) |
map.get(key) | Value or undefined | Retrieve by key |
map.has(key) | Boolean | Check key existence |
map.delete(key) | Boolean | Remove entry |
map.clear() | undefined | Remove all entries |
map.size | Number | Count of entries |
map.keys() | MapIterator | Iterate keys |
map.values() | MapIterator | Iterate values |
map.entries() | MapIterator | Iterate [key, value] pairs |
map.forEach(fn) | undefined | Run fn(value, key, map) |
Map.groupBy(iter, fn) | Map | Group iterable by key function |
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.
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
- Keys MUST be objects (not primitives like numbers, strings, booleans)
- Not iterable ยท no
for...of, nokeys(), novalues(), noentries(), nosize, noclear()
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
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:
Data for object 1
trueWeakMap Keys MUST Be Objects
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
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| Method | Returns | Description |
|---|---|---|
wm.set(objKey, value) | WeakMap | Add/update entry (key must be object) |
wm.get(objKey) | Value or undefined | Retrieve value by object key |
wm.has(objKey) | Boolean | Check if key exists |
wm.delete(objKey) | Boolean | Remove entry |
โ ๏ธ WATCH OUT: There is no
wm.size,wm.clear(),wm.keys(),wm.values(),wm.entries(), orwm.forEach(). WeakMap intentionally cannot be iterated ยท items may vanish at any time due to garbage collection.
Automatic Memory Cleanup ยท The Key Benefit
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:
// 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:
Deposited $500. New balance: $1500
Balance: 1500
Owner: AliceReal-World Use Case 2 ยท Caching Computed Results Per Object
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:
Computing...
{ processed: true, data: 42 }
Cache hit!
{ processed: true, data: 42 }Real-World Use Case 3 ยท DOM Element Metadata
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
| Feature | Map | WeakMap |
|---|---|---|
| Key types | Any value | Objects ONLY |
| Value types | Any value | Any value |
| Key references | Strong | Weak (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 use | General key-value storage | Private data, caches, DOM metadata |
Quick reference for every Map and WeakMap feature.
Map ยท Constructor
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 MapMap ยท Properties
| Property | Type | Description |
|---|---|---|
map.size | Number | Count of key-value entries |
map[Symbol.iterator] | Function | Makes Map iterable (same as entries()) |
Map ยท Instance Methods
| Method | Returns | Description |
|---|---|---|
map.set(key, value) | Map | Add/update entry; chainable |
map.get(key) | Value or undefined | Get value by key |
map.has(key) | Boolean | True if key exists |
map.delete(key) | Boolean | Remove entry; true if found |
map.clear() | undefined | Remove all entries |
map.keys() | MapIterator | Iterate keys in insertion order |
map.values() | MapIterator | Iterate values in insertion order |
map.entries() | MapIterator | Iterate [key, value] pairs |
map.forEach(fn) | undefined | Call fn(value, key, map) for each |
Map ยท Static Methods
| Method | Returns | Description |
|---|---|---|
Map.groupBy(iterable, keyFn) | Map | Group elements by key function result |
Map ยท Conversion Cheat Sheet
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
new WeakMap() // empty
new WeakMap([[objKey, value], ...]) // from entries (keys must be objects)WeakMap ยท Instance Methods (Only Four)
| Method | Returns | Description |
|---|---|---|
wm.set(objKey, value) | WeakMap | Add/update entry (key must be object) |
wm.get(objKey) | Value or undefined | Get value by object key |
wm.has(objKey) | Boolean | True if key exists |
wm.delete(objKey) | Boolean | Remove entry |
Choosing the Right Structure ยท Decision Guide
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 โ WEAKSETPhase 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:
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:
Amara
2Task A ยท Build the Directory
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:
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 RashidSelf-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 beforeget()when the value could beundefined?
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:
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:
3
2Task A ยท Full Word Counter
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:
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': 5Self-check questions:
- Why is a Map better than a plain object for counting words?
- Why does
counts.get(word) || 0work 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:
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); // 1Task A ยท Sales Grouping
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:
=== 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,100Self-check questions:
- What does
Map.groupByreturn and how is it different fromArray.reduce? - Why is the result a
Mapand not a plain object? - How would you find the region with the highest total sales after grouping?
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:
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:
- โ 1. Background: Why Maps Exist
- โ 2. Topic 1 ยท Maps: Basics & Creation
- โ 3. Topic 2 ยท Map Methods
- โ 4. Topic 3 ยท WeakMap
- โ 5. Topic 4 ยท Complete Map Reference
- โ 6. Applied Exercises