The Testing Academy · Class Notes Monday, 2 November (IST)
Live class · study guide

JavaScript multidimensional arrays and star patterns

Multidimensional arrays: a 2D array is an array of arrays, read with grid[row][col], row first. Then nested loops over a test matrix, process.stdout.write, row sums with map and reduce, and three star patterns: the right-hand triangle, the left-hand triangle and the pyramid. Files 140 to 146.

By Pramod Dutta, The Testing Academy. Study notes from the fifteenth class of Playwright 4x, built from the session recording, the batch repository, which received files 140 to 146 and two cheat-sheet images just after the class ended (commit "feat: add chapter 18 on 2D arrays and patterns"), and the Eraser notes deck, whose section for this class holds the definition, the matrix and grid examples, the loop and the note on process.stdout.write that the class worked from. Every file on this page was run with Node 22, and the output shown is what it prints. The trace tables come from runs of the class's own files with every loop check logged, and the three patterns were also run with n set to 1 and 5. The two short console.log and process.stdout.write snippets are rebuilt from the recording, then run.

Hides every output until you tap Reveal, so you can predict it first.
01

What this class covered

  • Arrays of arrays: 1D and 2D
  • Rows and columns on a test-data sheet
  • Reading a cell: row first, then column
  • Nested loops over a test matrix
  • Printing without a new line
  • Row sums with map and reduce
  • Pattern 1: the right-hand triangle
  • Pattern 2: the left-hand triangle, and ELI5
  • Pattern 3: the pyramid
  • Tasks: practise the three patterns
  • A 2D array is an array of arrays: each inner array is a row, and each item inside a row is a column.
  • grid[row][col] reads the row first, then the column, and both indexes start at 0.
  • grid.length gives the number of rows, and grid[0].length the number of columns.
  • Reading a row that does not exist, grid[7][7] on a 3 by 3 grid, throws a TypeError.
  • In nested loops, the outer loop (i) walks the rows and the inner loop (j) walks the columns.
  • process.stdout.write prints without a new line, so console.log("") ends each row.
  • In a star pattern, the inner loop's condition sets the stars on row i: j <= i in files 144 and 145.
02

Arrays of arrays: 1D and 2D

The last class covered string methods and objects (the 30 October notes); this one took up multidimensional arrays, chapter 18 in the repo.

A 1D ARRAY IS ONE LIST. A 2D ARRAY IS A LIST OF ROWS 1D: one list, one index let results = ["pass", "fail", "pass"]; file 140 pass 0 fail 1 pass 2 values index 2D: an array of arrays (the deck's matrix) THE OUTER ARRAY let matrix = [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]; column 0 column 1 column 2 row 0 1 2 3 row 1 4 5 6 row 2 7 8 9 Each inner array is a row, and each item inside a row is a column.
One list needs one index. An array of arrays needs two: the first picks a row, the second an item in it.

When a learner asked whether callbacks were due to start, the instructor said multidimensional arrays were still remaining and would come first, because a lot of important interview questions come from this topic; callbacks follow after. It is a very small topic, in the instructor's words, but one to cover.

The deck's definition, which the instructor read out: a normal (1D) array is a single list, and a multi-dimensional array is an array of arrays, like a table with rows and columns. The most common is the 2D array. File 140 is the 1D example from the deck, a plain list:

140_MDArrays.jsJavaScript
// 1D array,list - duplicate element
let results = ["pass", "fail", "pass"];

It only creates the list, so it prints nothing. A 2D array puts arrays inside an array, and the deck adds the rule to remember: each inner array is a row, and each item inside a row is a column. The examples, the instructor said, all come from the class notes.

03

Rows and columns on a test-data sheet

Two panels on cream paper. Left, titled One list, with the sublabel 1D array: a single paper strip of three cells, each holding a grey squiggle. Right, titled A table, with the sublabel 2D array: three rows of the same cells stacked in a wooden tray so they line up in columns; the middle row is tinted blush as one row, and a terracotta outline runs down the middle cell of every row as one column.
The class's picture of a 2D array: a table with rows and columns, where each row is a list of its own.

To show rows and columns, the instructor opened a Google Sheet of test data for a Playwright login page, a perfect example of a two-dimensional array in the instructor's words. Each line of cells across is a row, row 1, row 2 and so on, and the green line of cells down the sheet is a column. Rows and columns are totally different, so do not confuse them. The deck links the sheet: TestData_PW_Login.

The instructor then selected blocks of cells and asked how many rows and how many columns each one has. Each block still carries its label on the sheet:

Label on the sheet Rows Columns Read out in class as
3x3 3 3 3 rows and 3 columns, a 3-by-3 matrix
9x2 9 2 9 by 2
4x1 4 1 4 rows and 1 column
1x4 1 4 1 row and 4, 1 by 4

With only one row, the instructor said, it is almost similar to a 1D array, and when a learner asked whether one row or one column makes it 1D, the answer was yes. In code, a one-row table written with two pairs of brackets is still an array of arrays: [[10, 20, 30, 40]].length is 1, and each item takes two indexes, such as [0][3].

04

Reading a cell: row first, then column

A cheat sheet titled 2D Arrays: grid[row][col]. A 3 by 3 grid of the numbers 10 to 90, with the column numbers 0, 1 and 2 across the top and the row numbers 0, 1 and 2 down the side. The cell holding 60, in row 1 and column 2, is filled green, and a callout reads grid[1][2] = 60. On the right, two panels: grid.length points to 3, labelled rows, and grid[0].length points to 3, labelled columns. A banner at the bottom reads: Row first, then column.
From the batch repo's README: the grid from file 141, before line 10 changes its first cell. grid[1][2] is row 1, then column 2.

On the iPad, the instructor drew a 3 by 3 matrix of the numbers 1 to 9, the same values as the deck's matrix. The advice: always draw the rows first, row 0, row 1 and row 2, then the columns 0, 1 and 2. A lot of people have a confusion here, the instructor said, and drawing this diagram gives you the clarity. Two cells were then fetched by hand: 0, 0 gives 1, and 2, 1 gives 8. The deck's matrix, with those two lookups added and printed:

JavaScript
let matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
console.log(matrix[0][0]);
console.log(matrix[2][1]);
Output
1
8

File 141 reads, changes and measures a grid of the numbers 10 to 90, the grid on the cheat sheet:

141_2D.jsJavaScript
let grid = [
    [10, 20, 30],
    [40, 50, 60],
    [70, 80, 90]
];

console.log(grid[0][0]);
// console.log(grid[7][7]);

grid[0][0] = 99;
console.log(grid);

console.log(grid.length); // Row -> 3
console.log(grid[0].length); // Columns -> 3
Output
10
[ [ 99, 20, 30 ], [ 40, 50, 60 ], [ 70, 80, 90 ] ]
3
3
  • Line 7, grid[0][0], is row 0, then column 0: it prints 10.
  • Line 8 reads outside the grid. The instructor asked it as an interview question: what will happen? It gives an error, cannot read the property of undefined. With line 8 switched on, the file prints 10, then stops:
JavaScript
console.log(grid[7][7]);
Output
10
141_2D.js:8
console.log(grid[7][7]);
                   ^

TypeError: Cannot read properties of undefined (reading '7')

There is no row 7, so grid[7] is undefined, and reading [7] from undefined throws. The repo's README adds the other half: a column that does not exist, on a row that does, only gives undefined. Run after lines 1 to 5 of the file:

JavaScript
console.log(grid[1][2]);
console.log(grid[0][7]);
Output
60
undefined

grid[1][2] is the cheat sheet's 60: row 1, then column 2. grid[0][7] is undefined, with no error.

  • Line 10 changes a value. Can the grid change? Of course it will change, the instructor said: it is mutable in nature. So line 11 prints 99 where 10 was.
  • grid.length always gives you the rows, 3, and a row's length gives the columns: grid[0].length, also 3. In this grid, everything is 3.
05

Nested loops over a test matrix

To visit every cell, you need a loop inside a loop. On the iPad, the instructor drew it over the 3 by 3 grid: i starts at 0 and runs while i < 3, going in the row direction, and j starts at 0 and runs while j < 3, going in the column direction. Printing the value at i, j then visits the cells in order, row by row. A learner put it as: the first loop goes through the rows and the next one through the columns, and the instructor agreed.

Where is this useful? Two-dimensional arrays are also helpful in test data, the instructor said, and there are a lot of coding interview questions on them: the pattern programs. When a learner asked for a real example, the instructor showed a test matrix: each row holds a test case, its status and its status code.

142_REAL_Exmaple.jsJavaScript
let testMatrix = [
    ["login", "pass", 200],
    ["checkout", "fail", 404],
    ["search", "pass", 180]
];

for(let i=0;i< testMatrix.length;i++){
    for(let j=0;j< testMatrix[i].length;j++){
        process.stdout.write(testMatrix[i][j]+" ");
    }
    console.log("");
}

console.log(" --------- ");
Output
login pass 200 
checkout fail 404 
search pass 180 
 --------- 

The outer loop runs once per row, i < testMatrix.length, and the inner loop once per cell of that row, j < testMatrix[i].length. The same file, with every pass and every check of the inner loop logged:

Row Column Cell Written
0 0 "login" "login "
0 1 "pass" "pass "
0 2 200 "200 "
0 3 none: 3 < 3 is false console.log("") ends the line
1 0 "checkout" "checkout "
1 1 "fail" "fail "
1 2 404 "404 "
1 3 none: 3 < 3 is false console.log("") ends the line
2 0 "search" "search "
2 1 "pass" "pass "
2 2 180 "180 "
2 3 none: 3 < 3 is false console.log("") ends the line

process.stdout.write adds each cell and a space to the current line, which is why every printed line ends with a space, and console.log("") ends the line once the row is done. Line 14 then prints the divider. The deck's copy of this loop prints each cell with console.log(testMatrix[i][j] + " ") instead, so every cell lands on a line of its own, nine lines in all; the repo's version, above, keeps each row on one line.

06

Printing without a new line

From the deck:

Text
process.stdout.write -> print without the new line
console.log() ->we will print with a new line

console.log adds a new line after what it prints; process.stdout.write does not, so the next output carries on along the same line. When a learner asked what without a new line means, the instructor showed it with two words, first with console.log:

JavaScript
console.log("hi");
console.log("bye");
Output
hi
bye

Then with process.stdout.write:

JavaScript
process.stdout.write("hi");
process.stdout.write("bye");
Output
hibye

No new line: both words land in the same row. Asked to explain it, the instructor said process.stdout.write is a function, and process a library already present, used to write to the console through standard input and output.

process comes from Node.js. process is an object that Node.js provides, not part of the JavaScript language: in a browser's console, typeof process is "undefined", so process.stdout.write fails there. The repo's README makes the same point, and adds that file 146's way, building each row as a string and printing it with console.log(row), also works in a browser console.

07

Row sums with map and reduce

MAP TAKES EACH ROW, AND REDUCE ADDS UP ITS CELLS scores.map(row => row.reduce((a,b) => a+b)) each row reduce: a + b then the next cell total [85, 90, 78] 85 + 90 = 175 175 + 78 = 253 253 [60, 45, 70] 60 + 45 = 105 105 + 70 = 175 175 [95, 88, 92] 95 + 88 = 183 183 + 92 = 275 275 MAP COLLECTS [ 253, 175, 275 ] one total per row, in the rows' order reduce starts with a = the first cell and b = the second, then adds each next cell to the running total.
File 143 on its own data: two additions per row, and map keeps one total for each row.

Before the patterns came a couple of array functions. File 143 holds a student score matrix, one row per student:

143_array_Fn.jsJavaScript
let scores = [
    [85, 90, 78],   // student 0 , 253
    [60, 45, 70],   // student 1,  175
    [95, 88, 92]    // student 2, 275
];

// Matrix -> Row = Col -> 

let rowsSum = scores.map(row => row.reduce((a,b) => a+b));
console.log(rowsSum);
Output
[ 253, 175, 275 ]

To get the sum of each row, the instructor called scores.map with an arrow function, and reduce inside it, a crazy feature in the instructor's words. The sums of the rows are 253, 175 and 275, as the comments on lines 2 to 4 say.

How it works, as the instructor explained it: map takes one row at a time, and reduce takes the first two elements and adds them, 85 plus 90 gives 175, then takes 175 plus 78, which gives 253. Asked how it moves from one row to the next, the instructor said: automatically, because the arrow function is given each row in turn, row 0, row 1 and row 2. When a learner remarked that only two variables are used, the instructor said two inputs are taken, a plus b, an output comes out, and then another two are taken. The same file, with every addition logged:

Row Step Running total Next cell Sum
0 1 85 90 175
0 2 175 78 253
1 1 60 45 105
1 2 105 70 175
2 1 95 88 183
2 2 183 92 275

a starts as the row's first cell and b as the second; after that, a is the sum so far. map keeps each row's last sum, so rowsSum is [ 253, 175, 275 ]. map itself came up in the 19 October notes.

A matrix does not have to be square. Line 7's comment reads Matrix -> Row = Col, and in class a matrix was described as rows and columns being almost equal. That fits this file, 3 rows by 3 columns. A 2D array can have any number of rows and columns, like the 9 by 2 selection on the sheet, and in JavaScript its rows can even have different lengths, as the repo's README notes.

08

Pattern 1: the right-hand triangle

WHERE THE STARS GO, ROW BY ROW File 144: i = 0, 1, 2 j = 0 j = 1 j = 2 i = 0 0,0 * 0,1 0,2 i = 1 1,0 * 1,1 * 1,2 i = 2 2,0 * 2,1 * 2,2 * File 145: i = 3, 2, 1 j = 1 j = 2 j = 3 i = 3 3,1 * 3,2 * 3,3 * i = 2 2,1 * 2,2 * 2,3 i = 1 1,1 * 1,2 1,3 A star wherever j <= i. A star wherever j <= i, with i counting down. Dashed cells print nothing: the inner loop stops as soon as j > i, so no space is printed there either.
The same rule, j <= i, in both files. File 144 counts i up from 0; file 145 starts i at n and counts down, so the long row comes first.

Pattern programs come up in interviews: this question sometimes will come in your interview, the instructor warned, so pay attention. The first pattern prints one star, then two, then three. It resembles a triangle: a kind of right-hand triangle, the instructor said, or a 90-degree right triangle, a left triangle, whatever you call it. A learner called it a right-angle triangle, and the instructor agreed.

The instructor's way in starts with a drawing of what the instructor calls favourite lines: a 3 by 3 grid with every cell labelled by its row and column, from 0, 0 to 2, 2. The stars sit at 0, 0; at 1, 0 and 1, 1; and at 2, 0, 2, 1 and 2, 2. The pattern behind them: a star wherever j is less than or equal to i, and a space the moment j is greater than i. That is the core logic, so the inner loop runs only while j <= i:

144_Right_Hand_Pattern.jsJavaScript
// n = 3
// *
// * *
// * * *

let n = 3;

for(let i=0;i<n;i++){
    for(let j=0;j<=i;j++){
        // console.log("*");
        process.stdout.write("*");
    }
    console.log("");
}
Output
*
**
***

The same file, with every loop check logged:

Outer loop Stars printed at Stars Inner loop stops at Line
i = 0 j = 0 1 j = 1: 1 <= 0 is false *
i = 1 j = 0, 1 2 j = 2: 2 <= 1 is false **
i = 2 j = 0, 1, 2 3 j = 3: 3 <= 2 is false ***
i = 3 outer check 3 < 3 is false: the loop ends

So row i gets i + 1 stars. The code never prints the spaces from the drawing: the inner loop stops at the first j above i, and nothing more is written on that line.

Why process.stdout.write. Line 10 is the console.log("*") the class tried first. With it switched back on, and line 11 commented out:

JavaScript
console.log("*");
// process.stdout.write("*");
Output
*

*
*

*
*
*

console.log adds a new line after every star, so the stars stack up one per line, with a blank line from console.log("") after each row. That new line is not wanted, so line 11 writes each star with process.stdout.write, and a new line is added only when one row has finished. Leave out line 13, console.log(""), and everything ends up on the same line:

JavaScript
// console.log("");
Output
******

Any n. You can replace the 3 in the loop with n, the instructor said, and it works the same; change n to 5 and it prints 5 rows. With line 6 changed to this:

JavaScript
let n = 5;
Output
*
**
***
****
*****

With let n = 1; it prints a single star. Whatever n the user gives, the logic works.

The comment draws spaces that the code does not print. Lines 2 to 4 draw the triangle as * * *, with a space between the stars. The code writes "*" on its own, so the rows print as *, ** and ***. The repo's README points this out too.

09

Pattern 2: the left-hand triangle

The logic of the first triangle will not work for another one, the instructor said: the left-hand triangle, which is the reverse, upside down. Described for n equal to 5, it has five stars, then four, three, two and one. To work it out, the class took a smaller example, n equal to 3, because 3 is easy to work with, and drew the favourite lines again: three stars, then two, then one.

The logic: start i from n and go in reverse, i--, for as long as i >= 1. j starts normally, from 1, and runs while j <= i, so that j never goes beyond i. Each row ends with a new line, as before.

145_Left_Hand_Triablge_Pattern.jsJavaScript
// *****
// ****
// ***
// **
// *


let n = 3;
for(let i=n;i>=1;i--){
    for(let j=1;j<=i;j++){
        process.stdout.write("*");
    }
    console.log("");
}
Output
***
**
*

The same file, with every loop check logged:

Outer loop Stars printed at Stars Inner loop stops at Line
i = 3 j = 1, 2, 3 3 j = 4: 4 <= 3 is false ***
i = 2 j = 1, 2 2 j = 3: 3 <= 2 is false **
i = 1 j = 1 1 j = 2: 2 <= 1 is false *
i = 0 outer check 0 >= 1 is false: the loop ends

With i at 3, j goes 1, 2, 3 and three stars print; then i becomes 2 and j prints only two; then only one. The comment on lines 1 to 5 draws the five-row triangle the instructor first described. With line 8 changed to this, the file prints exactly that:

JavaScript
let n = 5;
Output
*****
****
***
**
*

With let n = 1; it prints one star. If the logic does not click yet, the instructor's advice was to remember it: even if you are not able to work it out, you can still remember it, and remember the logic too. That also works.

ELI5. For a better explanation, the instructor pointed to the ELI5 skill from the last class: ask it to explain the program. Later in the class, the instructor showed an ELI5 explanation of this triangle, a page you step through with a play button and next steps: with i at 3, j goes 1, 2, 3 and prints three stars, then 1, 2, then 1. Best for understanding, in the instructor's words, and the instructor made the page public so the class could see it. Asked whether ELI5 needs Claude's paid version, the instructor said the free version also works, though without being certain of it. Asked whether it works in Copilot too, the instructor hoped so, but had not tested it.

10

Pattern 3: the pyramid

A cheat sheet titled Pyramid: Spaces + Stars. Five numbered columns, 1 to 5, over three rows. Row 1 has two dashed empty boxes, then one star in column 3, labelled i = 1: 2 spaces, 1 star. Row 2 has one empty box, then three stars in columns 2 to 4, labelled i = 2: 1 space, 3 stars. Row 3 has five stars, labelled i = 3: 0 spaces, 5 stars. On the right, two formula cards: spaces = n - i, and stars = 2 * i - 1. A banner at the bottom reads n = 3 rows.
From the batch repo's README: each row of file 146 is n - i spaces, then 2 * i - 1 stars.

A third pattern also comes up a lot of times: the pyramid. The instructor called it a little complex, and then very complex: one star, then three, then five, like a pyramid. Look at the rows and there are spaces before the stars too; on the iPad the instructor marked each one with an O, O or space, whatever you want to call it.

The logic puts two loops inside the row loop. i goes from 1 to n, top to bottom. Each row starts as an empty string; the first inner loop adds the spaces, and the second adds the stars, 2 * i - 1 of them: 1 star for the first row, 3 for the second, and 5 for the third, 6 minus 1.

146_Pyamid_Pattern.jsJavaScript
//   *
//  ***
// *****

let n = 3;

for(let i=1;i<=n;i++){
        let row = "";
        for(let j=1;j<=n-i;j++){
               row += " ";
        }
        for(let j=1;j<=2*i-1;j++){
            row = row + "*";
        }
        console.log(row);
}
Output
  *
 ***
*****

The same file, with every space and every star logged:

Outer loop Spaces Stars Row length
i = 1 3 - 1 = 2 2 * 1 - 1 = 1 3
i = 2 3 - 2 = 1 2 * 2 - 1 = 3 4
i = 3 3 - 3 = 0 2 * 3 - 1 = 5 5

The spaces come only before the stars: nothing is added after them, so the rows are 3, 4 and 5 characters long. With line 15 printing JSON.stringify(row) and the length instead, the quotes show where each row starts and ends:

JavaScript
console.log(JSON.stringify(row), row.length);
Output
"  *" 3
" ***" 4
"*****" 5

With line 5 changed to this, the same loops build five rows:

JavaScript
let n = 5;
Output
    *
   ***
  *****
 *******
*********

With let n = 1;, they build a single star and no spaces.

Take your time with this one. The instructor asked everyone to rewatch the recording: it is a little difficult to understand as a beginner.

11

Tasks and announcements

  • Practise the three patterns: in coding practice, on your own. Today the class did them, but you have to practise them yourself, and the instructor asked Meeti to add the three there as questions.
  • Finish the last class's coding practice. A lot of the class had done it, and a lot had not; the instructor asked everyone to complete it today, if possible. The five string problems are in the 30 October notes.
  • Rewatch the recording if the pyramid is not clear yet.
  • Doubts go in the doubt thread for multi-dimensional arrays in JavaScript, which is already created.
  • The code is pushed: files 140 to 146 and the two cheat sheets went into the repo's 18_Multi_Dimensions_Array folder just after the class.
  • What comes next: callbacks, once multidimensional arrays are done, as the instructor said at the start; the repo's README still lists chapter 18 as in progress. JavaScript is not finished either: asked whether this was its last topic, the instructor said no, there is no last topic, and callbacks, sync and async are still to come. The plan, as the class opened: complete a couple of important JavaScript topics, then TypeScript and Playwright side by side.

Task. Practise the three pattern programs on your own: the right-hand triangle (one, two, then three stars), the left-hand triangle (three, two, then one) and the pyramid (one, three, then five stars, with spaces before them).

Interview questions. What happens when you read outside a 3 by 3 grid, as line 8 of file 141 does with grid[7][7]? And the pattern programs: print the right-hand triangle, the left-hand triangle and the pyramid.

Also worth revising from today: how do you get the number of rows and of columns? Why does a pattern use process.stdout.write, and why console.log("") after each row? What does scores.map(row => row.reduce((a,b) => a+b)) give for the class's scores?

Show the answer

Answer. grid[7] is undefined, because there is no row 7, so reading [7] from it throws TypeError: Cannot read properties of undefined (reading '7'); a missing column on a real row, grid[0][7], only gives undefined. The patterns are files 144, 145 and 146 above: a star wherever j <= i; the same with i counting down from n; and n - i spaces, then 2 * i - 1 stars. grid.length gives the rows and grid[0].length the columns, 3 and 3 for the class's grid. process.stdout.write keeps a row's stars on one line, and console.log("") adds the new line once the row is done. For the class's scores, map and reduce give [ 253, 175, 275 ], one total per row.