eduKatePunggol · Practical learning guide
Find your next learning step
Choose a route through JavaScript Map to understand the mechanism, check a worked example and plan the next practice.
Full chapter index · Practice and parent questions · How Studying Works
Your child has made a small JavaScript project, but looking up one item keeps producing the wrong answer. A book code written as a number works while the same code written as text does not. A missing entry looks exactly like an entry whose value is unknown. If you are supporting a coding learner in Punggol, the next useful step is to make the relationship between a key and its value visible.
JavaScript Map stores key–value entries and provides methods such as set(), get() and has() for working with them. It is different from an array's map() method, which transforms array elements. Begin with a fictional lending shelf: a short book code is the key, and the information attached to that code is the value. The learner's job is to explain how the program recognises a key before building a larger interface.
This Punggol learning guide develops that idea through a local-style school project using invented records. It covers key identity, missing entries, insertion order, reference sharing and a checked counting exercise. Computing enrichment should fit the child's current interests and foundations; this lesson does not imply that every school requires JavaScript or that a particular tuition class offers it. A small working model is enough to make meaningful progress.
Choose a chapter
Understand keyed lookup · 1–4
Check values and identity · 5–8
Iterate and count · 9–12
Validate and export · 13–16
Imagine three fictional books labelled B01, B02 and B03. A project needs to answer, “How many copies of B02 are available?” The question supplies the key. The program should return the value associated with that key.
const copies = new Map([
["B01", 2],
["B02", 0],
["B03", 5]
]);
console.log(copies.get("B02")); // 0
Zero is a useful first example. It means the book is listed but no copies are available. It is different from a book that is absent from the shelf's records. A learner who replaces every falsy result with “not found” will lose that distinction.
Before writing code, draw three labelled envelopes. Put a count inside each one. Ask which envelope the question identifies and what can be learned from its contents. This representation helps a child separate the identifier used to find an entry from the information stored there.
const doubled = [2, 0, 5].map(n => n * 2);
console.log(doubled); // [4, 0, 10]
This array operation visits elements and produces transformed results. new Map() creates a keyed collection. Their similar names do not make their jobs interchangeable.
Ask the learner which question each operation answers. “Give me the value for book B02” suggests a lookup. “Make a new list with every count doubled” suggests an array transformation. This is more useful than memorising that one name begins with a capital letter.
A project can use both. You might convert a Map's entries into an array and then transform them into display rows. Name the stages clearly: lookup collection, entry list, display rows. The learner should know which structure exists at each stage.
If a tutorial casually says “map the books,” ask what operation it means in that context. Technical vocabulary becomes much easier to learn when it is attached to a concrete input and output.
copies.set("B04", 3);
copies.set("B02", 1);
console.log(copies.size); // 4
console.log(copies.get("B02")); // 1
Adding a new key increases the number of entries. Setting an existing key changes that key's value. It does not create two independent entries with the same key.
This is an important modelling decision. If a project needs a history of every lending event, a single count per book is not itself that history. Repeatedly setting the count replaces the current value. A separate event list might be needed to retain changes over time.
For the first lesson, keep one job: current copies by book code. Then make the learner predict the size before running each set. Include both an existing code and a new code. If they predict that every set increases size, the misunderstanding is now visible and easy to repair.
set() returns the Map, so calls can be chained. Chaining is optional; separate lines are often clearer while the child is learning what changes.
const notes = new Map([["B01", undefined], ["B02", "checked"]]);
console.log(notes.get("B01")); // undefined
console.log(notes.get("B99")); // undefined
console.log(notes.has("B01")); // true
console.log(notes.has("B99")); // false
The two get results are alike, but the entries are not. B01 exists with an undefined value; B99 is absent. When that difference matters, use has().
Write two separate questions beside the code: “Is the key present?” and “What value is stored?” A program can require both answers. For example, a display might show “record not created” for absence and “note not supplied” for an existing entry with no note.
Do not turn every absence into the same reassuring default. That can hide incomplete input and make a project appear more complete than it is. Define the states the interface needs to communicate, then preserve the distinctions required by those states.
This also connects with the optional chaining lesson: avoiding an access error is different from establishing that the requested information exists.
const shelf = new Map([["B01", 0]]);
const wrong = shelf.get("B01") || "not listed";
const clear = shelf.has("B01") ? shelf.get("B01") : "not listed";
console.log(wrong); // 'not listed'
console.log(clear); // 0
The || expression treats zero as a reason to use the fallback. That is wrong for this contract. The presence check expresses the intended question directly.
Nullish coalescing, ??, can be suitable when only null and undefined should trigger a fallback. But it still does not distinguish a missing key from a present key storing undefined. Choose it because its rule matches the task, not because it looks modern.
Add a boolean example such as a book code mapped to whether its cover has been checked. false can be a legitimate stored result. The child should explain why “not checked” and “no record” may need different messages.
A small program becomes trustworthy when it handles the quiet edge cases, not only when it displays the most convenient positive count.
const labels = new Map();
labels.set(7, "numeric key");
labels.set("7", "text key");
console.log(labels.size); // 2
console.log(labels.get(7)); // 'numeric key'
console.log(labels.get("7")); // 'text key'
These are different keys. A form field usually supplies text, even when the characters look numeric. If another stage stored numeric keys, a text lookup can fail without either value looking obviously wrong on screen.
For book codes, this guide uses strings deliberately. A code such as B01 is an identifier, not a quantity to calculate with. Preserve leading zeroes and any letters required by the code system.
If a project accepts several input forms, define one normalisation rule at the boundary. Do not sprinkle conversions throughout the program and hope they agree. Test the conversion independently, then use the resulting type consistently for storage and lookup.
Ask the learner to write both a value and its type in their trace notes. Seeing 7: number and "7": string is often enough to locate the error.
const first = {code: "B01"};
const second = {code: "B01"};
const owners = new Map([[first, "Mina"]]);
console.log(owners.get(first)); // 'Mina'
console.log(owners.get(second)); // undefined
The objects contain equal-looking fields, but they are different objects. A Map using object keys recognises the reference, not an automatic comparison of their contents.
This can be useful when the identity of a particular object is the intended key. It can be confusing when a learner expects reconstructed records to match earlier ones. If the project needs lookup by book code, use the code itself as the key instead of a newly created wrapper object.
Return to the envelope model: two envelopes with identical writing are still two physical envelopes. The analogy is imperfect, but it helps distinguish matching contents from the same object.
For a checkpoint exercise, assign const third = first and predict owners.get(third). The answer is Mina because third holds the same reference. A learner who explains this has grasped something more durable than one Map method.
const special = new Map();
special.set(NaN, "not a number");
console.log(special.get(NaN)); // 'not a number'
special.set(0, "zero");
special.set(-0, "updated zero");
console.log(special.size); // 2
Map key equality treats NaN as matching NaN and treats positive and negative zero as the same key. These details matter when learning the actual equality rule; they should not become an excuse to use invalid numeric input as an identifier.
In the shelf project, reject an invalid book code before inserting it. If a parsing operation produces NaN, investigate why. A collection's ability to store that value does not establish that it belongs in your data model.
Keep this chapter as a boundary check after ordinary string and object keys are secure. A beginner does not need a dozen unusual values in their first example. The purpose is to stop an oversimplified explanation such as “Map keys always behave exactly like every other comparison in JavaScript.”
const order = new Map([["B02", 1], ["B01", 2]]);
order.set("B02", 9);
console.log([...order.keys()]); // ['B02', 'B01']
order.delete("B02");
order.set("B02", 9);
console.log([...order.keys()]); // ['B01', 'B02']
Updating the value of an existing key does not move it to the end. Deleting and then adding that key creates a new insertion position.
Do not call this “automatically alphabetical.” The order reflects entry history. If a display needs alphabetical book codes, convert the entries to a list and sort that view deliberately. Preserve the Map's lookup job while giving the report its own ordering rule.
Have the child describe the output as a sequence of events: add B02, add B01, update B02, delete B02, add B02. The event trace makes the two outputs easy to explain without guessing.
Insertion order is useful, but it is not a substitute for a report contract. Readers may interpret the first row as highest priority unless the display explains what the order represents.
for (const [code, count] of copies) {
console.log(code, count);
}
Each visit provides a two-item entry. The destructuring pattern names those items. Keeping them together makes it harder to print a count beside the wrong code.
Use keys() when only identifiers are needed and values() when only stored values are needed. Use entries() or the Map itself when the connection matters. Avoid building separate key and value arrays and reconnecting them later unless the task genuinely requires that extra structure.
The JavaScript destructuring lesson can help a learner who does not understand [code, count]. Return here once they can explain the two positions.
For predictable beginner exercises, avoid changing the Map while iterating it. First decide which entries need changes, then perform those changes in a clear second stage. That keeps the trace manageable while the learner establishes the core model.
copies.forEach((count, code) => {
console.log(code, count);
});
Map's forEach callback receives the value first, then the key, then the Map. That differs from the two-item entry order in a for...of destructuring pattern. Names in a callback do not change the arguments supplied by the method.
A learner might write (code, count) because those names sound natural. The program will still pass the value into the first parameter. If the output appears reversed, inspect the callback contract instead of randomly swapping stored data.
Use one entry with very different-looking key and value types, such as B01 and 2. An example where both are short numbers can conceal the mistake. Distinguishable inputs make the argument order obvious.
Either iteration style can be readable. Choose one for the first project and explain it well. Learning several styles at once is less helpful than being able to trace one correctly.
Count invented requests for book codes. Each request adds one to that code's total.
const requests = ["B01", "B02", "B01", "B03", "B01"];
const totals = new Map();
for (const code of requests) {
totals.set(code, (totals.get(code) ?? 0) + 1);
}
console.log([...totals]);
// [['B01', 3], ['B02', 1], ['B03', 1]]
Here the stored values are always counts. Before a code is first inserted, the lookup is undefined and the initial count is zero. After insertion, a valid numeric count is present. That invariant makes the fallback suitable in this particular loop.
Trace all five visits on paper. Record the incoming code, previous count and new count. If the child cannot explain the second B01 visit, reduce the input to two repeated codes before adding other entries.
The total of all final counts should equal the number of accepted requests. This is an independent check on whether the loop lost or duplicated an event.
Suppose the exercise accepts codes with surrounding spaces and treats letter case as insignificant. Then define that rule before counting.
function normaliseCode(value) {
if (typeof value !== "string") {
throw new TypeError("Book code must be text");
}
const code = value.trim().toUpperCase();
if (!/^B\d{2}$/.test(code)) {
throw new RangeError("Expected B followed by two digits");
}
return code;
}
This is our teaching format, not a universal book-code standard. It accepts " b01 " as B01 and rejects "B1". Another project could choose different rules.
Do not uppercase personal names or alter meaningful case simply because this works for our invented identifiers. Normalisation can merge values that a different system considers distinct.
Test accepted and rejected inputs before using the function inside a larger interface. A clear error at the boundary is easier to diagnose than an unexplained missing entry several steps later.
const imported = new Map([["B01", 2], ["B01", 5]]);
console.log(imported.size); // 1
console.log(imported.get("B01")); // 5
The later value replaces the earlier value for that key. If the input represents two batches of copies to add together, this constructor alone does not perform that addition. If it represents a later correction, replacement may be appropriate.
Write the input meaning before choosing the operation. “Current count updates” and “new stock events” sound similar but require different rules. A Map cannot infer which one the author intended.
For an import that should contain unique codes, check has(code) before inserting and report duplicates. For event totals, add values deliberately after validating them. For latest-state updates, document that later records win.
Ask the learner to produce a two-row counterexample for the chosen policy. The exercise is complete when they can explain why the alternative policy would produce the wrong meaning, even if both programs run without errors.
CHAPTER 15 OF 20 · Validate and export
15. Keep object values and their references clear
const detail = {copies: 2};
const books = new Map([["B01", detail]]);
books.get("B01").copies = 7;
console.log(detail.copies); // 7
The Map stores the object reference. Getting it does not create a deep copy. A learner who updates the returned object is changing that same object.
Likewise, new Map(existingMap) creates a new collection of entries while retaining references to object values. Adding a key to the new Map does not add it to the original Map, but changing a shared object's field can be visible through both.
Draw the Maps as two lists of arrows pointing to one record box. Then draw a separate new record box to represent an actual copied object. This is more precise than saying “the Map was copied, so everything is independent.”
For this beginner counter, numeric values keep the model simple. Introduce object values only when the project needs several fields per code and the learner can explain what is shared.
CHAPTER 16 OF 20 · Validate and export
16. Export data through a deliberate representation
const data = new Map([["B01", 2], ["B02", 0]]);
console.log(JSON.stringify(data)); // '{}'
const text = JSON.stringify([...data]);
const restored = new Map(JSON.parse(text));
console.log(restored.get("B02")); // 0
A plain Map does not automatically serialise its entries through ordinary JSON.stringify. An array of entries is a useful representation for this string-key, number-value exercise.
Do not generalise this round trip to every possible key and value. JSON has limits: object-key identity, undefined values and other JavaScript values require their own decisions. Validate parsed data before treating it as trusted project input.
Ask what the saved text must preserve. For our shelf, code strings and finite counts are sufficient. For a project using object keys, recreating equal-looking objects would not preserve their original identity relationship.
Saving is a modelling task. A successful parse proves that the text has valid JSON syntax; it does not prove that every entry follows the shelf's data contract.
Use explicit expected results: B01 has three requests; B02 and B03 have one each; B99 is absent; there are three distinct codes. Check the sum as five. Those checks assess different parts of the counter.
Then change the input. Try an empty list, one request and several repeated requests. Add spaces and lower case only if the normalisation rule allows them. Add an invalid code and check that it is rejected visibly.
For presence handling, build an entry with zero and another with undefined. Ask the learner to explain which tests should use has and which should use get. A test limited to positive numbers will not expose the falsy-value bug from chapter 5.
Finally, intentionally replace ?? with a suitable wrong rule or remove the increment. The expected results should fail. A test earns its place by detecting a meaningful error, not by repeating the implementation in another function.
Count ['B02', 'B01', 'B02', 'B03', 'B02', 'B01']. Predict both the counts and the entry order before running the loop.
The counts are B02: 3, B01: 2 and B03: 1. The insertion order is B02, B01, B03, because that is when each key first appears. It is not descending count order by definition, even though it happens to look that way for this input.
Change the requests to ['B03', 'B01', 'B02', 'B02', 'B02', 'B01']. The counts stay the same, but insertion order becomes B03, B01, B02. This counterexample prevents the learner from mistaking an accidental pattern for a guarantee.
As a transfer task, create an alphabetical display without changing the stored totals. Explain the difference between the collection used for lookup and the ordered view used for reading. Both structures can be useful because they serve different questions.
CHAPTER 19 OF 20 · Test and practise
19. Make a small Punggol practice session useful
After school or CCA, choose a task that can be finished and explained with six fictional requests. First predict, then trace, then run, then change one condition. The next session can begin with a new input so the learner retrieves the method rather than copies yesterday's code.
Parents can ask, “What is the key's type?”, “Does this key exist?” and “What meaning does zero have here?” These questions require no complicated programming vocabulary and reveal the main distinctions.
If the child keeps creating a new object for each lookup, return to identity. If repeated requests overwrite counts incorrectly, return to the update rule. If the display order looks wrong, return to the view contract. The remedy should match the failure.
When discussing computing support with a teacher or tutor, clarify the actual topic and level available. Ask for an explanation of the learner's small program and one changed-input task. The quality of that reasoning is a better guide than how large or colourful the first demonstration looks.
For the shelf exercise, write: codes are validated strings in the B00 format; values are request counts; repeated requests increment totals; unknown codes are absent; display order is chosen separately. This short statement keeps the program's meaning available after the lesson.
The next useful challenge is not necessarily another collection type. It may be an input that tests a distinction the child previously missed: zero versus absence, object contents versus identity, or update order versus insertion order.
Use the How Studying Works guide to organise later retrieval and transfer practice. Bring back a small program whose behaviour the learner can predict and explain.
Technical references: MDN's Map overview, Map.get, Map.has, Map.set and Map.forEach. The examples here use invented data so that the collection's rules remain easy to inspect.

