Episode 1 — Fundamentals / 1.21 — Arrays
1.21.g -- Multidimensional Arrays
In one sentence: A multidimensional array is an array whose elements are themselves arrays -- commonly used for matrices, grids, and tables -- accessed with chained brackets like
matrix[row][col]and iterated with nested loops.
Navigation: <- 1.21.f -- Practice Problems . 1.21 Overview
1. What is a multidimensional array?
A multidimensional array is simply an array containing other arrays. The most common form is a 2D array (array of arrays), which models a grid with rows and columns.
// 1D array -- a list
const list = [1, 2, 3, 4, 5];
// 2D array -- a grid (matrix)
const grid = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
// 3D array -- a cube (rarely needed)
const cube = [
[[1, 2], [3, 4]],
[[5, 6], [7, 8]]
];
Visualization of a 2D array:
col 0 col 1 col 2
┌───────┬───────┬───────┐
row 0 │ 1 │ 2 │ 3 │
├───────┼───────┼───────┤
row 1 │ 4 │ 5 │ 6 │
├───────┼───────┼───────┤
row 2 │ 7 │ 8 │ 9 │
└───────┴───────┴───────┘
2. Creating a 2D array (matrix)
Literal syntax
// 3x3 matrix
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
// 2x4 matrix (2 rows, 4 columns)
const table = [
[10, 20, 30, 40],
[50, 60, 70, 80]
];
// Matrix with mixed data (spreadsheet-like)
const spreadsheet = [
["Name", "Age", "Score"],
["Alice", 25, 95],
["Bob", 30, 82],
["Carol", 28, 91]
];
Programmatic creation
// Create a 3x4 matrix filled with zeros
const rows = 3;
const cols = 4;
// Correct way: Array.from with a factory function
const matrix = Array.from({ length: rows }, () =>
new Array(cols).fill(0)
);
console.log(matrix);
// [[0, 0, 0, 0],
// [0, 0, 0, 0],
// [0, 0, 0, 0]]
// WRONG way: fill with the same array reference
const bad = new Array(3).fill(new Array(4).fill(0));
bad[0][0] = 99;
console.log(bad);
// [[99, 0, 0, 0],
// [99, 0, 0, 0], <-- all rows share the same inner array!
// [99, 0, 0, 0]]
Why the "wrong way" fails: fill() uses the same reference for every slot. All three rows point to the same inner array, so modifying one modifies all.
Create with nested for loops
function createMatrix(rows, cols, defaultValue = 0) {
const matrix = [];
for (let r = 0; r < rows; r++) {
const row = [];
for (let c = 0; c < cols; c++) {
row.push(defaultValue);
}
matrix.push(row);
}
return matrix;
}
const m = createMatrix(3, 3, 0);
console.log(m); // [[0,0,0], [0,0,0], [0,0,0]]
Identity matrix
function identityMatrix(n) {
return Array.from({ length: n }, (_, r) =>
Array.from({ length: n }, (_, c) => (r === c ? 1 : 0))
);
}
console.log(identityMatrix(3));
// [[1, 0, 0],
// [0, 1, 0],
// [0, 0, 1]]
3. Accessing elements: matrix[row][col]
Access is done by chaining bracket notation: first the row, then the column.
const matrix = [
[10, 20, 30],
[40, 50, 60],
[70, 80, 90]
];
// Access row 0, col 2
console.log(matrix[0][2]); // 30
// Access row 2, col 0
console.log(matrix[2][0]); // 70
// Access the center element
console.log(matrix[1][1]); // 50
// Modify an element
matrix[1][2] = 99;
console.log(matrix[1]); // [40, 50, 99]
Breaking down matrix[r][c]:
// Step 1: matrix[1] returns the inner array [40, 50, 60]
const row = matrix[1];
console.log(row); // [40, 50, 60]
// Step 2: row[2] returns the element at column 2
const element = row[2];
console.log(element); // 60 (or 99 after modification above)
// Combined: matrix[1][2]
Getting dimensions:
const rows = matrix.length; // 3 (number of rows)
const cols = matrix[0].length; // 3 (number of columns in first row)
console.log(`Matrix is ${rows}x${cols}`); // Matrix is 3x3
4. Iterating with nested loops
Standard nested for loop
const matrix = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]
];
// Print every element
for (let r = 0; r < matrix.length; r++) {
for (let c = 0; c < matrix[r].length; c++) {
process.stdout.write(String(matrix[r][c]).padStart(3));
}
console.log(); // newline after each row
}
// 1 2 3 4
// 5 6 7 8
// 9 10 11 12
Using for...of
const grid = [
["a", "b", "c"],
["d", "e", "f"],
["g", "h", "i"]
];
for (const row of grid) {
for (const cell of row) {
process.stdout.write(cell + " ");
}
console.log();
}
// a b c
// d e f
// g h i
With indices using entries()
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
for (const [r, row] of matrix.entries()) {
for (const [c, val] of row.entries()) {
console.log(`matrix[${r}][${c}] = ${val}`);
}
}
// matrix[0][0] = 1
// matrix[0][1] = 2
// ... etc.
Iterating column-first
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
// Column by column (outer loop = columns, inner = rows)
for (let c = 0; c < matrix[0].length; c++) {
const column = [];
for (let r = 0; r < matrix.length; r++) {
column.push(matrix[r][c]);
}
console.log(`Column ${c}:`, column);
}
// Column 0: [1, 4, 7]
// Column 1: [2, 5, 8]
// Column 2: [3, 6, 9]
5. 3D arrays (briefly)
A 3D array adds another nesting level -- think of it as multiple layers of 2D grids.
// 2x2x2 cube
const cube = [
[ // layer 0
[1, 2],
[3, 4]
],
[ // layer 1
[5, 6],
[7, 8]
]
];
// Access: cube[layer][row][col]
console.log(cube[0][1][0]); // 3 (layer 0, row 1, col 0)
console.log(cube[1][0][1]); // 6 (layer 1, row 0, col 1)
// Iterate with triple nested loop
for (let l = 0; l < cube.length; l++) {
for (let r = 0; r < cube[l].length; r++) {
for (let c = 0; c < cube[l][r].length; c++) {
console.log(`cube[${l}][${r}][${c}] = ${cube[l][r][c]}`);
}
}
}
3D arrays are rare in everyday web development but appear in game engines (voxels), scientific computing, and color image data (height x width x RGB channels).
6. Common operations
Sum all elements in a matrix
function matrixSum(matrix) {
let total = 0;
for (const row of matrix) {
for (const val of row) {
total += val;
}
}
return total;
}
const m = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
console.log(matrixSum(m)); // 45
Transpose a matrix (rows become columns)
function transpose(matrix) {
const rows = matrix.length;
const cols = matrix[0].length;
const result = Array.from({ length: cols }, () => []);
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
result[c].push(matrix[r][c]);
}
}
return result;
}
const original = [
[1, 2, 3],
[4, 5, 6]
];
console.log(transpose(original));
// [[1, 4],
// [2, 5],
// [3, 6]]
Visualized:
Original (2x3): Transposed (3x2):
┌───┬───┬───┐ ┌───┬───┐
│ 1 │ 2 │ 3 │ │ 1 │ 4 │
├───┼───┼───┤ → ├───┼───┤
│ 4 │ 5 │ 6 │ │ 2 │ 5 │
└───┴───┴───┘ ├───┼───┤
│ 3 │ 6 │
└───┴───┘
Matrix addition
function matrixAdd(a, b) {
const result = [];
for (let r = 0; r < a.length; r++) {
const row = [];
for (let c = 0; c < a[r].length; c++) {
row.push(a[r][c] + b[r][c]);
}
result.push(row);
}
return result;
}
const A = [[1, 2], [3, 4]];
const B = [[5, 6], [7, 8]];
console.log(matrixAdd(A, B));
// [[6, 8], [10, 12]]
Search in a matrix
function findInMatrix(matrix, target) {
for (let r = 0; r < matrix.length; r++) {
for (let c = 0; c < matrix[r].length; c++) {
if (matrix[r][c] === target) {
return { row: r, col: c };
}
}
}
return null; // not found
}
const grid = [
[10, 20, 30],
[40, 50, 60],
[70, 80, 90]
];
console.log(findInMatrix(grid, 50)); // { row: 1, col: 1 }
console.log(findInMatrix(grid, 99)); // null
Rotate a matrix 90 degrees clockwise
function rotate90(matrix) {
const n = matrix.length;
const result = Array.from({ length: n }, () => new Array(n));
for (let r = 0; r < n; r++) {
for (let c = 0; c < n; c++) {
result[c][n - 1 - r] = matrix[r][c];
}
}
return result;
}
const m = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
console.log(rotate90(m));
// [[7, 4, 1],
// [8, 5, 2],
// [9, 6, 3]]
7. Jagged arrays (rows of different lengths)
Unlike matrices (rectangular), jagged arrays have rows of different lengths.
const jagged = [
[1, 2, 3],
[4, 5],
[6, 7, 8, 9],
[10]
];
console.log(jagged.length); // 4 (rows)
console.log(jagged[0].length); // 3
console.log(jagged[1].length); // 2
console.log(jagged[2].length); // 4
console.log(jagged[3].length); // 1
Iterating jagged arrays safely:
for (let r = 0; r < jagged.length; r++) {
for (let c = 0; c < jagged[r].length; c++) { // use jagged[r].length, not a fixed col count
process.stdout.write(String(jagged[r][c]).padStart(3));
}
console.log();
}
// 1 2 3
// 4 5
// 6 7 8 9
// 10
Real-world uses of jagged arrays:
- Student grades (different number of assignments per student)
- File system (directories with different numbers of files)
- Social network (friends lists of different lengths)
const studentGrades = [
{ name: "Alice", grades: [95, 82, 91, 88, 97] },
{ name: "Bob", grades: [78, 85, 72] },
{ name: "Carol", grades: [90, 93, 87, 91] }
];
for (const student of studentGrades) {
const avg = student.grades.reduce((a, b) => a + b, 0) / student.grades.length;
console.log(`${student.name}: ${student.grades.length} grades, avg = ${avg.toFixed(1)}`);
}
// Alice: 5 grades, avg = 90.6
// Bob: 3 grades, avg = 78.3
// Carol: 4 grades, avg = 90.3
8. Real examples
Tic-Tac-Toe board
// Initialize empty board
function createBoard() {
return [
[" ", " ", " "],
[" ", " ", " "],
[" ", " ", " "]
];
}
// Display the board
function displayBoard(board) {
console.log(" 0 1 2");
for (let r = 0; r < board.length; r++) {
console.log(`${r} ${board[r].join(" | ")}`);
if (r < board.length - 1) {
console.log(" ---------");
}
}
}
// Place a mark
function placeMark(board, row, col, mark) {
if (board[row][col] !== " ") {
console.log("Cell already taken!");
return false;
}
board[row][col] = mark;
return true;
}
// Check for a winner
function checkWinner(board) {
// Check rows
for (let r = 0; r < 3; r++) {
if (board[r][0] !== " " && board[r][0] === board[r][1] && board[r][1] === board[r][2]) {
return board[r][0];
}
}
// Check columns
for (let c = 0; c < 3; c++) {
if (board[0][c] !== " " && board[0][c] === board[1][c] && board[1][c] === board[2][c]) {
return board[0][c];
}
}
// Check diagonals
if (board[0][0] !== " " && board[0][0] === board[1][1] && board[1][1] === board[2][2]) {
return board[0][0];
}
if (board[0][2] !== " " && board[0][2] === board[1][1] && board[1][1] === board[2][0]) {
return board[0][2];
}
return null;
}
// Demo game
const board = createBoard();
placeMark(board, 0, 0, "X");
placeMark(board, 1, 1, "O");
placeMark(board, 0, 1, "X");
placeMark(board, 2, 0, "O");
placeMark(board, 0, 2, "X"); // X wins row 0
displayBoard(board);
// 0 1 2
// 0 X | X | X
// ---------
// 1 | O |
// ---------
// 2 O | |
const winner = checkWinner(board);
console.log(winner ? `Winner: ${winner}` : "No winner yet");
// Winner: X
Spreadsheet data
const sheet = [
["Product", "Q1", "Q2", "Q3", "Q4"],
["Widgets", 1200, 1500, 1100, 1800],
["Gadgets", 800, 950, 1200, 1050],
["Gizmos", 450, 520, 480, 610]
];
// Calculate row totals
console.log("Sales Report:");
console.log("=".repeat(50));
for (let r = 1; r < sheet.length; r++) {
const product = sheet[r][0];
let total = 0;
for (let c = 1; c < sheet[r].length; c++) {
total += sheet[r][c];
}
console.log(`${product}: ${total} units (avg ${(total / 4).toFixed(0)}/quarter)`);
}
// Widgets: 5600 units (avg 1400/quarter)
// Gadgets: 4000 units (avg 1000/quarter)
// Gizmos: 2060 units (avg 515/quarter)
// Calculate column totals
console.log("\nQuarterly totals:");
for (let c = 1; c < sheet[0].length; c++) {
let colTotal = 0;
for (let r = 1; r < sheet.length; r++) {
colTotal += sheet[r][c];
}
console.log(`${sheet[0][c]}: ${colTotal}`);
}
// Q1: 2450
// Q2: 2970
// Q3: 2780
// Q4: 3460
Pixel grid (simplified)
// A 5x5 pixel grid using grayscale values (0-255)
const pixels = [
[ 0, 0, 0, 0, 0],
[ 0, 255, 255, 255, 0],
[ 0, 255, 0, 255, 0],
[ 0, 255, 255, 255, 0],
[ 0, 0, 0, 0, 0]
];
// Visualize as ASCII art
function renderPixels(grid) {
for (const row of grid) {
let line = "";
for (const pixel of row) {
line += pixel > 128 ? "##" : " ";
}
console.log("|" + line + "|");
}
}
renderPixels(pixels);
// | |
// | ###### |
// | ## ## |
// | ###### |
// | |
// Invert the image
function invertPixels(grid) {
return grid.map(row => row.map(pixel => 255 - pixel));
}
console.log("\nInverted:");
renderPixels(invertPixels(pixels));
// |##########|
// |## ##|
// |## ## ##|
// |## ##|
// |##########|
9. Practice: Tic-Tac-Toe board + Matrix addition
Exercise 1: Check if a tic-tac-toe board is full
function isBoardFull(board) {
for (const row of board) {
for (const cell of row) {
if (cell === " ") return false;
}
}
return true;
}
// Test
const fullBoard = [["X","O","X"],["O","X","O"],["O","X","O"]];
const partialBoard = [["X"," ","X"],["O","X","O"],["O","X","O"]];
console.log(isBoardFull(fullBoard)); // true
console.log(isBoardFull(partialBoard)); // false
Exercise 2: Matrix addition (try it yourself first!)
// Given:
const M1 = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
const M2 = [
[9, 8, 7],
[6, 5, 4],
[3, 2, 1]
];
// Expected result of M1 + M2:
// [[10, 10, 10],
// [10, 10, 10],
// [10, 10, 10]]
function addMatrices(a, b) {
const result = [];
for (let r = 0; r < a.length; r++) {
const row = [];
for (let c = 0; c < a[r].length; c++) {
row.push(a[r][c] + b[r][c]);
}
result.push(row);
}
return result;
}
console.log(addMatrices(M1, M2));
// [[10, 10, 10], [10, 10, 10], [10, 10, 10]]
Key takeaways
- A 2D array is an array of arrays -- access with
matrix[row][col]. - Create matrices with
Array.from()and a factory function -- neverfill()with a shared array reference. - Iterate 2D arrays with nested loops -- outer loop for rows, inner for columns.
- Jagged arrays have rows of different lengths -- always use
matrix[r].lengthfor the inner bound. - Transpose swaps rows and columns; rotation maps
[r][c]to[c][n-1-r]. - 2D arrays model real-world grids: game boards, spreadsheets, images, calendars.
- 3D arrays add another layer of nesting and are used in specialized domains.
Explain-It Challenge
Explain without notes:
- Why does
new Array(3).fill([])create a broken 2D array, and how do you fix it? - How do you access the element in the second row, third column of a matrix?
- To iterate a matrix column-by-column instead of row-by-row, which index becomes the outer loop?
- Draw a 3x3 tic-tac-toe board as a 2D array and show how you would check if the middle row is all "X".
Navigation: <- 1.21.f -- Practice Problems . 1.21 Overview