0 / 91
Week 4 · Day 22 of 91

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.

  1. In ~/fullstack-journey/inventory-app/reports.js, paste the five-component array above and add two components of your own. Run node reports.js to confirm the file executes.
  2. Low-stock report. Use filter for quantity < 10, then map each result into a line like "U1 NE555 timer — 3 left". Print with console.log(lines.join("\n")). Confirm only low items appear.
  3. Category report. Pick one category, filter to it, and reduce those to a unit count. Check the number by adding the quantities yourself on paper.
  4. Total-value report. Reuse the reduce from the walkthrough and print it with toFixed(2).
  5. Sorted report. Copy with [...components], sort descending by value on hand (b.quantity * b.unitPrice - a.quantity * a.unitPrice), and print the top three using slice(0, 3). Then print the original array and confirm its order did not change.
  6. Use find once to look up a component by id, and once with an id that does not exist. Print both results and note that the second is undefined.

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 explicit return; without braces the value is returned for you.
  • Using forEach and pushing into an array. It works, but map or filter states the intent in one line. Save forEach for when you genuinely want a side effect, like printing.
  • sort without a comparison function. Numbers get compared as text, so 100 lands before 2. Always pass (a, b) => a - b for numbers.
  • Reaching for reduce first. If the result is a list of the same length, that is map. If it is a shorter list, that is filter. reduce is 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.

  1. This lesson covered five methods. Find some and every in MDN, and write one sentence each on what they return and when you would use them instead of filter.
  2. MDN documents reduce with 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

  1. 0–5 min Recall

    Without notes, state yesterday’s main idea and one unresolved question.

  2. 5–20 min Learn

    Read only the listed concept notes and official reference sections needed today.

  3. 20–48 min Build

    Generate low-stock, category, and total-value reports from component objects.

  4. 48–55 min Explain and verify

    Run the result, inspect evidence, and explain the data/control flow in your own words.

  5. 55–60 min Quiz and commit

    Complete the quiz, record one lesson, and commit the verified change when applicable.

What to hand in

Deliverable

A report script using at least four appropriate array methods.

Working with AI today

AI as skeptical reviewer

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

Q1 Which method returns items that pass a condition?
Q2 Which result best proves today’s work is complete?
Q3 What should an AI code review primarily produce?

Explain-back gate

Pass the quiz above to unlock completion.

Quiz + explain-back checks required.