JavaScript Basics: Transform Arrays Without Changing the Original
A list often needs several small changes before it is ready to display: remove unavailable items, calculate a value for each remaining item, and put the results in order. JavaScript has array methods for each job. This tutorial builds a tiny stock summary, then handles numeric sorting and empty input explicitly.
Set up an isolated workspace
You need Linux, Bash, the mktemp, rm, and rmdir utilities, a text editor, and Node.js on your PATH. The examples were tested with Node.js v24.19.0 on Linux. No packages, downloads, network access, or environment variables are required. The .mjs extension enables ES modules, including imports from Node’s built-in assertion module.
Run this in Bash and keep the same terminal open through cleanup:
node --version
start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1The tested version prints:
v24.19.0Use your editor to save each named file below as UTF-8 in this temporary directory. Each file runs independently; there is no shared setup code to copy between files.
Select, transform, and total
Use filter() to choose entries and map() to calculate their output values. Keeping those jobs separate makes the intermediate results easy to inspect. A filter callback answers “keep this?”, while a map callback returns the new value. MDN documents filtering and mapping.
Save as summarize-stock.mjs:
import assert from "node:assert/strict";
const stock = [
{ name: "Notebook", quantity: 3, priceCents: 250 },
{ name: "Pen", quantity: 0, priceCents: 120 },
{ name: "Folder", quantity: 2, priceCents: 175 },
];
const before = JSON.stringify(stock);
const available = stock.filter((item) => item.quantity > 0);
const lines = available.map((item) => ({
name: item.name,
valueCents: item.quantity * item.priceCents,
}));
const totalCents = lines.reduce((sum, item) => sum + item.valueCents, 0);
assert.equal(totalCents, 1100);
assert.equal(JSON.stringify(stock), before);
assert.equal(available[0], stock[0]);
assert.notEqual(lines[0], stock[0]);
console.log(JSON.stringify(lines));
console.log(`Total cents: ${totalCents}`);Run:
node summarize-stock.mjsExpected output:
[{"name":"Notebook","valueCents":750},{"name":"Folder","valueCents":350}]
Total cents: 1100The parentheses around the map callback’s object literal make it an expression to return. Without them, braces would start a function body and require an explicit return.
reduce() carries an accumulated value through the array. Here sum starts at the supplied 0, and each callback returns the next sum. An explicit starting value also makes an empty list safe. See MDN’s reduce reference.
These callbacks do not change the input. However, a new array does not mean deeply copied objects. The filtered array still refers to the original item objects; changing available[0].quantity would change the original item. The map callback deliberately creates new objects containing only the fields this report needs.
Sort numbers without rearranging the input
A default array sort compares string representations, which is surprising for numbers: 10 can precede 2. Supply a comparator for numeric ordering. A negative result puts the first value before the second; zero treats them as equal; a positive result puts it after. toSorted() returns a new array rather than rearranging its input. See MDN’s toSorted reference.
Save as sort-scores.mjs:
import assert from "node:assert/strict";
const scores = [10, 2, 30, 2];
const ascending = scores.toSorted((a, b) => a - b);
const descending = scores.toSorted((a, b) => b - a);
assert.deepEqual(ascending, [2, 2, 10, 30]);
assert.deepEqual(descending, [30, 10, 2, 2]);
assert.deepEqual(scores, [10, 2, 30, 2]);
assert.deepEqual([].toSorted((a, b) => a - b), []);
console.log(`Ascending: ${ascending.join(", ")}`);
console.log(`Original: ${scores.join(", ")}`);Run:
node sort-scores.mjsExpected output:
Ascending: 2, 2, 10, 30
Original: 10, 2, 30, 2This comparator assumes finite numbers. Validate data from outside your program before sorting; strings and NaN need an explicit policy. Avoid boolean comparators such as (a, b) => a > b, which do not supply the negative result required for the reverse ordering.
Give empty input a useful result
“No matching entries” is an ordinary outcome, not necessarily an error. Choose a result that callers can use without a special recovery path.
Save as summarize-empty.mjs:
import assert from "node:assert/strict";
function totalPositive(values) {
return values
.filter((value) => value > 0)
.map((value) => value * 2)
.reduce((sum, value) => sum + value, 0);
}
assert.equal(totalPositive([]), 0);
assert.equal(totalPositive([-3, 0]), 0);
assert.equal(totalPositive([-3, 0, 2, 5]), 14);
assert.deepEqual([].map((value) => value * 2), []);
assert.throws(() => [].reduce((sum, value) => sum + value), TypeError);
console.log(`Empty total: ${totalPositive([])}`);
console.log(`Mixed total: ${totalPositive([-3, 0, 2, 5])}`);Run:
node summarize-empty.mjsExpected output:
Empty total: 0
Mixed total: 14The assertions run silently when correct. An unexpected result stops the script, so successful output also confirms the checks passed.
Clean up
Remove only the files created for this tutorial, then remove the empty directory:
rm -- summarize-stock.mjs sort-scores.mjs summarize-empty.mjs
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dirReach for filter to select, map to transform, and reduce to combine. Keep the steps readable, preserve the input when needed, and decide what empty data means before it reaches your interface.
Acknowledgements
This post was created with assistance from AI.