Without notes, state yesterday’s main idea and one unresolved question.
Array transformations
JavaScript Foundations II and the Browser
Objective
Use map, filter, find, reduce, and sort with clear intent.
Events are interrupts, promises represent future results, and the DOM is the browser’s live model of the page.
- transformation versus mutation
- callback functions
- choosing the right method
Why this matters
On Day 19 you looped over an array with for...of and built up results by hand. That works, but it
buries the intent: a reader has to run the loop in their head to discover you were counting, or
picking, or converting. Today you learn five methods — map, filter, find, reduce, and
sort — that each say the intent in their name.
By the end of the hour you can turn a list of component objects into three different reports
without writing a single for loop, and you can say out loud which method belongs to which job.
Transformation versus mutation
There are two ways to get a changed list, and confusing them causes bugs that are genuinely hard to find.
Mutation modifies the original array in place. push, pop, splice, reverse, and sort
all mutate. After they run, the array you started with is different, and so is every other name
pointing at it.
Transformation leaves the original alone and returns a new array. map, filter, concat,
and slice all transform. Your source data survives untouched.
const quantities = [42, 8, 3];
const doubled = quantities.map((n) => n * 2);
console.log(doubled); // [ 84, 16, 6 ]
console.log(quantities); // [ 42, 8, 3 ] — unchanged
Prefer transformation. If three parts of a program each build their own view of the same data and none of them alter the source, none of them can break the others.
Signal chain versus rework
A filter stage takes a signal in and puts a new signal out; the source is still driving the
input, unchanged, and you can tap it. Mutation is desoldering the source component and replacing
it — every other branch fed by that node now sees something different, whether it wanted to or
not. map and filter are stages in a chain. sort and push are rework on the board.
`sort` mutates, and sorts as text by default
sort reorders the array you called it on. Copy first with [...items] — the spread ...
unpacks one array into a new one. And with no arguments, sort compares items as strings:
[10, 9, 100, 2].sort() returns [ 10, 100, 2, 9 ], because "100" sorts before "2". For
numbers you must pass a comparison function.
Callback functions
Every one of today's methods takes a callback: a function you hand to another function so it can call it for you, once per item. You do not call it yourself; the method does.
On Day 18 you wrote functions with function. There is a shorter spelling, the arrow function:
const double = (n) => n * 2; // arrow form
function doubleAgain(n) { return n * 2; } // Day 18 form — identical behaviour
Parameters in parentheses, then =>, then the body. When the body is a single expression, its
value is returned automatically — no return keyword, no braces. Arrow functions are what you will
see in real code for callbacks, so today you switch to them.
The method decides what to do with the answer; your callback decides what the answer is.
Handing over the instructions
You give a sorting office a stack of letters and one written rule: "put anything addressed overseas in the red bin." You do not stand there deciding letter by letter — the office runs your rule against each letter itself. The rule is the callback; the office is the method.
Choosing the right method
Here is the data used for the rest of today. It is your Day 20 component shape with a unitPrice
added, because today's reports involve money.
const components = [
{ id: "R1", name: "Resistor 10k", category: "resistor", quantity: 42, unitPrice: 0.02 },
{ id: "C1", name: "Capacitor 100nF", category: "capacitor", quantity: 8, unitPrice: 0.05 },
{ id: "U1", name: "NE555 timer", category: "ic", quantity: 3, unitPrice: 0.45 },
{ id: "D1", name: "LED red 5mm", category: "led", quantity: 120, unitPrice: 0.03 },
{ id: "U2", name: "ATmega328P", category: "ic", quantity: 2, unitPrice: 2.10 },
];
map — same number of items, each one converted. Five components in, five names out.
const names = components.map((item) => item.name);
console.log(names);
// [ 'Resistor 10k', 'Capacitor 100nF', 'NE555 timer', 'LED red 5mm', 'ATmega328P' ]
filter — returns the items that pass a condition. Your callback returns true to keep an
item and false to drop it. Fewer items out than in (or the same, or none), but the items
themselves are unchanged.
const lowStock = components.filter((item) => item.quantity < 10);
console.log(lowStock.map((item) => item.id)); // [ 'C1', 'U1', 'U2' ]
find — returns the first single item that passes, or undefined. Use it when you want one
thing, not a list.
console.log(components.find((item) => item.id === "U1").name); // NE555 timer
console.log(components.find((item) => item.id === "X9")); // undefined
That undefined is the trap: find does not throw when nothing matches, so the crash arrives one
line later when you read a property off it.
reduce — many items collapse into one value. It takes two arguments: a callback with an
accumulator and the current item, and a starting value.
const totalUnits = components.reduce((runningTotal, item) => runningTotal + item.quantity, 0);
console.log(totalUnits); // 175
The 0 at the end is the starting accumulator. Each pass, whatever your callback returns becomes
the accumulator for the next item. reduce is the general-purpose one and therefore the most
overused: if map or filter says it more clearly, use those.
sort — reorders. Pass a comparison function taking two items. Return a negative number to put
a first, positive to put b first, 0 to leave the order alone. a.quantity - b.quantity does
exactly that arithmetic, giving ascending order.
const byQuantity = [...components].sort((a, b) => a.quantity - b.quantity);
console.log(byQuantity.map((item) => item.quantity)); // [ 2, 3, 8, 42, 120 ]
console.log(components.map((item) => item.quantity)); // [ 42, 8, 3, 120, 2 ] — original safe
Thirty seconds, right now
Open a terminal, run node, and paste the components array in. Then run
components.filter((item) => item.category === "ic").length. You should get 2. You are talking
to the same JavaScript engine your files run in.
Walkthrough
Create ~/fullstack-journey/inventory-app/reports.js and build the total-value report together.
Start with the components array above, then add:
const totalValue = components.reduce(
(sum, item) => sum + item.quantity * item.unitPrice,
0,
);
console.log(`Total inventory value: ${totalValue.toFixed(2)}`);
node reports.js
Total inventory value: 10.39
toFixed(2) turns a number into a string with two decimal places — money should never be printed
raw. Now chain two methods. Each returns an array, so the next one can be called straight on it:
const icNames = components
.filter((item) => item.category === "ic")
.map((item) => `${item.name} (${item.quantity})`);
console.log(icNames);
[ 'NE555 timer (3)', 'ATmega328P (2)' ]
Read it left to right: keep the ICs, then convert each to a label. filter first is deliberate —
converting five items and discarding three does the same work with more steps.
Checkpoint
Say which method you would reach for to answer each: "how many units in total?", "which parts are below reorder level?", "where is part U1?", "list every part name". If any answer took more than two seconds, reread the section above.
Your turn
Build the deliverable: a report script using at least four appropriate array methods.
- In
~/fullstack-journey/inventory-app/reports.js, paste the five-component array above and add two components of your own. Runnode reports.jsto confirm the file executes. - Low-stock report. Use
filterforquantity < 10, thenmapeach result into a line like"U1 NE555 timer — 3 left". Print withconsole.log(lines.join("\n")). Confirm only low items appear. - Category report. Pick one category,
filterto it, andreducethose to a unit count. Check the number by adding the quantities yourself on paper. - Total-value report. Reuse the
reducefrom the walkthrough and print it withtoFixed(2). - Sorted report. Copy with
[...components],sortdescending by value on hand (b.quantity * b.unitPrice - a.quantity * a.unitPrice), and print the top three usingslice(0, 3). Then print the original array and confirm its order did not change. - Use
findonce to look up a component byid, and once with an id that does not exist. Print both results and note that the second isundefined.
You are done when
node reports.js prints four reports, you used at least four different array methods, and you
can name why each method was the right one.
Reviewer mode — after your script runs
Today's AI mode is reviewer: you bring finished work and ask for defects, not for code. A useful review produces specific, actionable findings backed by evidence from your code — never general praise and never a wholesale rewrite.
"Review whether each array method matches the operation. Flag unnecessary reduce usage."
For each finding, decide yourself whether it is right before changing anything. A review you apply without judging is just a rewrite you did not read.
Common pitfalls
- Forgetting
map's callback must return.components.map((item) => { item.name })gives[ undefined, undefined, ... ]. With braces you need an explicitreturn; without braces the value is returned for you. - Using
forEachand pushing into an array. It works, butmaporfilterstates the intent in one line. SaveforEachfor when you genuinely want a side effect, like printing. sortwithout a comparison function. Numbers get compared as text, so100lands before2. Always pass(a, b) => a - bfor numbers.- Reaching for
reducefirst. If the result is a list of the same length, that ismap. If it is a shorter list, that isfilter.reduceis for collapsing to a single value.
Verify it yourself
Open today's reference, MDN's Dynamic scripting with JavaScript, and find its pages on arrays and array methods.
- This lesson covered five methods. Find
someandeveryin MDN, and write one sentence each on what they return and when you would use them instead offilter. - MDN documents
reducewith and without an initial value. Find what happens when you omit the initial value on an empty array, then prove it by running the code.
Add both answers as comments at the bottom of reports.js. Finding the edge case yourself, before
it finds you in a real program, is the point.
The hour
- 0–5 min Recall
- 5–20 min Learn
Read only the listed concept notes and official reference sections needed today.
- 20–48 min Build
Generate low-stock, category, and total-value reports from component objects.
- 48–55 min Explain and verify
Run the result, inspect evidence, and explain the data/control flow in your own words.
- 55–60 min Quiz and commit
Complete the quiz, record one lesson, and commit the verified change when applicable.
What to hand in
A report script using at least four appropriate array methods.
Working with AI today
Provide existing work and ask for concrete defects, risks, missing tests, and unsupported assumptions—not praise.
Review whether each array method matches the operation. Flag unnecessary reduce usage.
References
End-of-day quiz
Explain-back gate
Pass the quiz above to unlock completion.
Quiz + explain-back checks required.