Module 7 ¡ Arrays and the Array Methods: The map, filter, reduce Pipeline
Arrays, the Picture: Ordered Lists and the map, filter, reduce Pipeline
In this lesson
- Store many values in one array, and read any of them by its index, the last one with
at(-1). - Grow an array with
pushand read its size withlength. - Picture
map,filterandreduceas stages that elements flow through, one element at a time.
Maria teaches a quiz class of thirty students. After every quiz she has thirty marks to keep. With what you know so far, that means thirty variables: mark1, mark2, and so on to mark30. Finding the average means typing thirty names in one long line. Adding a thirty-first student means changing the program in four places.
You have already met the fix, quietly. The starter that reads your input keeps every word of it in tokens, and tokens is an array. In this module you learn arrays properly: what they are and every method they have. You also learn the pipeline of map, filter and reduce, which turns most list jobs into one readable line.
An array is an ordered list
An array is one value that holds a list of other values, in order. Each value in it is an element. You write an array with square brackets and commas between the elements. This is called an array literal, because you write its contents literally in the code.
const marks = [72, 45, 88, 91, 60];
console.log(marks);
console.log(marks.length);
[ 72, 45, 88, 91, 60 ]
5
The first line prints the whole array. The second prints length, the number of elements. Order matters: [72, 45] and [45, 72] are two different arrays.
An array can hold any values, even a mix: numbers, strings, booleans, other arrays. In practice, keep one kind per array. A list of marks should hold only marks, so every element can be treated the same way.
Indexes start at 0
Each element sits at a numbered position called its index. The first element is at index 0, the second at index 1, and the last at index length - 1. You read an element with the array's name and the index in square brackets.
const marks = [72, 45, 88, 91, 60];
console.log(marks[0]);
console.log(marks[2]);
console.log(marks[marks.length - 1]);
console.log(marks[5]);
72
88
60
undefined
The last line asks for index 5, one past the end. JavaScript does not stop the program. It gives undefined, the value that means "nothing here". That is kind, and also dangerous: a wrong index does not shout, it just quietly gives you nothing.
Counting from 0 feels odd for a day or two. Think of the index as "how many elements come before this one". The first element has zero elements before it.
at(-1) reads from the end
Reading the last element as marks[marks.length - 1] is long, and Bob gets the - 1 wrong about once a week. Since ES2022, arrays have at. With a positive number it works like the brackets. With a negative number it counts from the end: at(-1) is the last element, at(-2) the one before it.
const marks = [72, 45, 88, 91, 60];
console.log(marks.at(0));
console.log(marks.at(-1));
console.log(marks.at(-2));
72
60
91
Square brackets do not count from the end. marks[-1] is undefined, because -1 is not an index at all.
An array grows
An array is not fixed in size. push adds an element to the end and makes length one bigger. This is how the starter builds out: every line of your answer is pushed onto it, then joined at the end.
const marks = [];
console.log(marks.length);
marks.push(72);
marks.push(45);
marks.push(88);
console.log(marks);
console.log(marks.length);
0
[ 72, 45, 88 ]
3
Notice the const. It does not freeze the array. It only means the name marks always points to the same array. The contents of that array can still change, which is why push works on a const array. Module 8 explains this in detail, with references and copies.
You can also write to an index directly: marks[1] = 50 replaces the second element. Writing far past the end leaves gaps, and Lesson 02 shows why that is a trap.
The pipeline picture
Most list jobs are one of three shapes. You want a new value for every element, like a mark with 5 grace marks added. You want only some elements, like the marks of 80 or more. Or you want one answer from all of them, like a total or a count.
JavaScript gives each shape its own method:
mapcalls your function once per element and builds a new array of the answers. The new array has the same length.filtercalls your function once per element and keeps the elements for which it returnedtrue. The new array can be shorter.reducewalks the array with a running value, and returns that one value at the end.
None of the three changes the array you call it on. Each returns something new, so you can chain them: the output of one stage is the input of the next. That chain is the pipeline.
const marks = [72, 45, 88, 91, 60];
const graced = marks.map((m) => m + 5);
const high = graced.filter((m) => m >= 80);
const count = high.reduce((c, m) => c + 1, 0);
console.log(graced);
console.log(high);
console.log(count);
console.log(marks);
[ 77, 50, 93, 96, 65 ]
[ 93, 96 ]
2
[ 72, 45, 88, 91, 60 ]
The last line proves the point: marks is exactly as it was. The function you pass to each stage is the arrow function of Module 5. (m) => m + 5 takes one element and returns its new value.
Watch the same pipeline one element at a time. Each step moves one element through one stage. The element 45 becomes 50 in map, then fails the test in filter, so it never reaches reduce.
A reduce step is easier to trust when you see its running value. Here is a total of the same marks, one row per element.
The last row's "sum after" is what reduce returns. The 0 at the end of the call is the start value: the running sum before the first element.
Arrays and the three stages
const a = [x, y, z]; // an array literal
a[0] a.at(-1) a.length // first element, last element, size
a.push(v); // add v at the end
a.map((el) => newValue) // new array, same length
a.filter((el) => trueOrFalse) // new array, the elements that passed
a.reduce((soFar, el) => next, start) // one value
Maria's input is the number of students, then their marks. This report prints the class average, the highest mark, and the marks of 80 or more. It reads with the starter's first two lines, which you know from Module 1.
const input = require("fs").readFileSync(0, "utf8");
const tokens = input.split(/\s+/).filter(Boolean);
const marks = tokens.slice(1).map(Number);
const total = marks.reduce((sum, m) => sum + m, 0);
const best = marks.reduce((hi, m) => (m > hi ? m : hi), marks[0]);
const high = marks.filter((m) => m >= 80);
console.log("average " + total / marks.length);
console.log("best " + best);
console.log("80 or more: " + high.join(" "));
average 73.33333333333333
best 91
80 or more: 88 91 84
Look at the third line of code. tokens.slice(1) drops the first token, the count, and map(Number) turns every remaining string into a number. That one line is a small pipeline of its own.
This program prints one line per stage: its name and the length of the array after it. The chart below draws those numbers.
const marks = [72, 45, 88, 91, 60, 84, 39, 77];
const graced = marks.map((m) => m + 5);
const high = graced.filter((m) => m >= 80);
console.log("input " + marks.length);
console.log("map " + graced.length);
console.log("filter " + high.length);
input 8
map 8
filter 4
Where this is used
- React: its documentation page "Rendering Lists" builds a list on screen by calling
mapover an array, andfilterto show only some items. - Node.js:
fs.readdirSyncreturns the names of the files in a folder as an array of strings. - V8: the engine inside Chrome and Node.js stores small arrays of whole numbers in a compact form; Lesson 05 looks inside.
Common mistakes
1. Reading one past the end
const marks = [72, 45, 88];
for (let i = 0; i <= marks.length; i++) {
console.log(marks[i]);
}
72
45
88
undefined
Bob wrote <= where < belongs. The loop runs four times for three elements, and the fourth read is index 3, which does not exist. JavaScript prints undefined and carries on, so the bug can travel far. Use i < marks.length, or better, a for...of loop that has no index to get wrong.
2. Calling push on something that is not an array yet
let marks;
marks.push(72);
console.log(marks);
TypeError: Cannot read properties of undefined (reading 'push')
Node stops with TypeError: Cannot read properties of undefined (reading 'push'). The variable was declared but never given an array, so it holds undefined, and undefined has no push. Start the list as an empty array: const marks = [];.
3. A missing comma between elements
const marks = [72 45 88];
console.log(marks.length);
Node refuses to run the file and says SyntaxError: Unexpected number. After 72 the parser expects a comma or the closing bracket. A space is not enough to separate two elements.
Maria runs the school library. When a book comes back, the desk writes how many days late it is. Zero or a negative number means the book came back on time or early. Every late day costs 5 taka. Maria wants the fine of each late book, in the order they came back, and the total of all fines.
Input. The first line holds n, the number of returned books. The second line holds n whole numbers: the days late of each book, in the order they came back.
Output. Two lines. The first line holds the fine of every late book, in their order, separated by single spaces, or the word none if no book was late. The second line holds the total of the fines.
Constraints. 1 <= n <= 100000; -30 <= each number of days <= 60.
Sample.
6
3 0 -2 10 1 0
15 50 5
70
const input = require("fs").readFileSync(0, "utf8");
const tokens = input.split(/\s+/).filter(Boolean);
let at = 0;
const next = () => tokens[at++];
const nextInt = () => Number(next());
const out = [];
// your code: read with next() and nextInt(), push every line of output to out
console.log(out.join("\n"));
Run in Compiler
Graded as late-returns, a free problem in this module's problem set.
David sells bus tickets. A return ticket costs twice the one-way fare. He wants every return fare on one line, then how many return fares are over 100 taka, then their total.
Input. n, then n whole one-way fares.
Output. three lines: the return fares in order separated by spaces, the count of return fares over 100, and the total of all return fares.
Constraints. 1 <= n <= 1000. 1 <= each fare <= 500.
Sample. the input below.
5
40 75 50 120 30
const input = require("fs").readFileSync(0, "utf8");
const tokens = input.split(/\s+/).filter(Boolean);
let at = 0;
const next = () => tokens[at++];
const nextInt = () => Number(next());
const out = [];
// your code: read with next() and nextInt(), push every line of output to out
console.log(out.join("\n"));
Sample output.
80 150 100 240 60
2
630
Run in Compiler
Kenji logs the temperature at noon each day. He wants the biggest change between two neighbouring days, up or down. He also wants the day it happened: the later of the two days, counting from 1. If two changes tie, report the earlier day.
Input. n, then n whole temperatures.
Output. one line: the biggest change, a space, and the day.
Constraints. 2 <= n <= 1000. -20 <= each temperature <= 50.
Sample. the input below.
6
21 25 19 19 30 27
const input = require("fs").readFileSync(0, "utf8");
const tokens = input.split(/\s+/).filter(Boolean);
let at = 0;
const next = () => tokens[at++];
const nextInt = () => Number(next());
const out = [];
// your code: read with next() and nextInt(), push every line of output to out
console.log(out.join("\n"));
Sample output.
11 5
Run in Compiler
Common doubts
Why does the index start at 0 and not 1?
Most languages that JavaScript grew from, like C, count from 0, because the index says how far an element is from the start. The first element is zero steps away. You get used to it within a week of practice.
Can one array hold numbers and strings together?
Yes, JavaScript allows it. But a mixed list is hard to process, because every stage must check what each element is. Keep one kind of value per array, and use an object (Module 8) when a record needs several kinds.
Is
at(-1)available everywhere?It joined the language in ES2022 and runs on Node 22 and every current browser. Very old browsers lack it, and there
a[a.length - 1]does the same job.Do I still need loops now that I have map, filter and reduce?
Yes. A loop is still the clearest tool when you must stop early or carry state from one element to the next. Lesson 04 gives the rule for choosing.
Key takeaways
- An array is an ordered list of elements, written
[a, b, c], withlengthas its size. - Indexes start at 0; a read past the end gives
undefined, not an error. at(-1)reads the last element; square brackets never count from the end.pushadds to the end, and works on aconstarray becauseconstfixes only the name.map,filterandreducereturn something new and leave the array as it was, so they chain into a pipeline.- Go deeper: Under the Hood, Arrays as Objects, V8's Elements Kinds, and What Holes, shift and Spread Cost (Pro).
Next: every array method, one by one, with what it returns and whether it changes the array.
End of lesson 1
Mark it done, and your progress moves with you.
Next: Every Array Method, One by One, With What It Returns