Command Palette

Search for a command to run...

Hectal
PHASE 5Beginner ~18 min· topic 9 of 10

Topic 5.9

Nested Arrays

In one line

An array can hold other arrays, which gives you grids and tables (2D arrays) read with two indexes like grid[row][col]; flat removes levels of nesting and flatMap maps and flattens one level in a single step.

Think of it like this

A chocolate box with rows of chocolates. The box holds rows, and each row holds chocolates. To pick one you say "row 2, the 3rd chocolate". That's a 2D array: an array of rows, where each row is itself an array. Emptying every row onto one plate, in order, is flat.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Nested array
An array that contains other arrays as items.
2D array
An array of rows, each row an array, like a table or a game board.
Row and column
In grid[r][c], r picks the row (the outer array's index) and c the item inside that row.
Nested loop
A loop inside another loop, used to visit every cell of a grid.
flat
Makes a new array with inner arrays unpacked into it, to a given depth.
flatMap
Maps each item to an array and flattens the results by one level, in one step.
Jagged array
A 2D array whose rows have different lengths.
Transpose
Swap rows and columns of a grid.

Step by step

01Build a grid

Write one row per line so the code looks like the table it represents.

app.jswhole filejavascript
const grid = [
  [1, 2, 3],
  [4, 5, 6],
];

02Read a cell: row, then column

grid[1] is the whole second row. [2] then picks its third item.

Read a cell: row, then columndiagram
Rendering diagram…

03Visit every cell

The outer loop picks a row; the inner loop walks along it.

app.jswhole filejavascript
for (let r = 0; r < grid.length; r++) {
  for (let c = 0; c < grid[r].length; c++) {
    console.log(r, c, grid[r][c]);
  }
}

04Make an empty grid safely

Each row must be its own new array, so build rows with a function.

app.jswhole filejavascript
const board = Array.from({ length: 3 }, () => Array(3).fill(""));

05Flatten when you need one list

flat() unpacks one level; pass a bigger depth or Infinity for more. flatMap maps and flattens one level together.

app.jswhole filejavascript
grid.flat();                     // [ 1, 2, 3, 4, 5, 6 ]
["a b", "c"].flatMap((s) => s.split(" ")); // [ 'a', 'b', 'c' ]

Try it yourself

  1. 1

    Check the other diagonal

    In the first example, get the anti-diagonal (top-right to bottom-left) with map. Hint: the column is row.length - 1 - i. Predict the values first.

  2. 2

    Count the X's

    In the first example, count every "X" on the board in one line using flat() and filter. Predict the number.

  3. 3

    Make it bigger

    In the third example, change board to 4 rows by 5 columns and put a # in the bottom-left corner. Which indexes do you need?

Code & diagrams

A tic-tac-toe board as a 2D array New tab

Row first, then column. The diagonal is the cells where the row and column numbers are equal. `columns` is the board transposed.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

O
3 3
[ 'X', 'X', 'X' ] true
[ 'X', '', '' ]
flat and flatMap ES2019+ New tab

`flat` and `flatMap` are ES2019. The original `nested` is unchanged; the console shows `[Array]` for arrays nested more than three levels deep. `flatMap` returning `[]` drops an item.

Console

(nothing was printed)

Expected console output

[ 1, 2, [ 3, [ 4 ] ] ]
[ 1, 2, 3, [ 4 ] ]
[ 1, 2, 3, 4 ]
[ 1, [ 2, [ 3, [Array] ] ] ]
[ 1, 3 ]
[ [ 'hello', 'world' ], [ 'hi', 'there' ] ]
[ 'hello', 'world', 'hi', 'there' ]
[ 1, 10, 3, 30 ]
The fill trap when building a grid New tab

`fill([])` created one inner array and put it in every slot. `Array.from` calls the function once per row, so each row is new. Filling with `"."` is fine: strings are values, not shared objects.

Console

(nothing was printed)

Expected console output

[ [ 'x' ], [ 'x' ], [ 'x' ] ]
true
[ [ 'x' ], [], [] ]
...
..#

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Filling a grid with one shared row

Build a board with const board = Array(3).fill(Array(3).fill("")); and set board[0][0] = "X";.

terminal
$ the page shows
── what you'll see ──
An X appears in the first column of every row, not just the top-left cell.

Break #2

Reading past the edge of a grid

Check a cell's neighbour with grid[r + 1][c] when r is the last row.

terminal
$ Console
── what you'll see ──
Uncaught TypeError: Cannot read properties of undefined (reading '0')

Myth vs fact

Myth

flat() flattens everything.

Fact

By default it flattens only one level. Pass a depth, or Infinity to flatten completely.

Myth

JavaScript has a built-in 2D array type.

Fact

There's no special type: a 2D array is an ordinary array whose items are arrays. Rows can even have different lengths.

Myth

flatMap is the same as map(...).flat(Infinity).

Fact

flatMap flattens exactly one level. Arrays nested deeper in your returned value stay nested.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    For large numeric grids (images, games, simulations), one flat typed array (new Float64Array(rows * cols)) indexed as r * cols + c is much faster and smaller than an array of arrays: one memory block, no per-row objects, better cache use.

  • ▸

    Loop row by row (outer loop over rows, inner over columns). That reads memory in order and is faster than going column by column, especially for big grids.

  • ▸

    flat skips holes, so arr.flat() doubles as a way to remove holes from a sparse array.

  • ▸

    structuredClone(grid) (Topic 5.10) or grid.map((row) => [...row]) copies a 2D array of primitives; [...grid] alone copies only the outer array, so the rows are still shared.

Remember this

  1. 1

    A nested array is an array whose items include other arrays: [1, [2, 3]]. The most useful kind is a 2D array (two-dimensional), an array of rows: const grid = [[1, 2, 3], [4, 5, 6]];. Read it with two indexes, row first, then column: grid[1][2] means "row 1, then item 2 of that row", which is 6. grid.length is the number of rows, and grid[0].length the number of columns.

  2. 2

    To visit every cell, use a nested loop: an outer loop over rows and an inner loop over the items of each row. for (const row of grid) { for (const cell of row) { ... } }. When you need positions, use classic for loops with r and c. Game boards, spreadsheets, seating plans and image pixels are all 2D arrays. Hectal's /dsa course uses grids for matrix and graph problems.

  3. 3

    Creating an empty grid has a famous trap. new Array(3).fill([]) puts the same inner array in all three slots (fill copies the reference, not the array), so changing one row changes them all. Make a fresh row for each slot with Array.from({ length: rows }, () => Array(cols).fill(0)).

  4. 4

    flat(depth = 1) (ES2019) returns a new array with nested arrays unpacked depth levels deep. [1, [2, [3]]].flat() gives [1, 2, [3]]; flat(2) or flat(Infinity) gives [1, 2, 3]. flat also removes holes. It doesn't change the original.

  5. 5

    flatMap(fn) (ES2019) is map followed by flat(1), done in one pass. Your function can return an array, and its items are spread into the result: return [] to drop an item, [x] to keep one, [x, y] to turn one into two. sentences.flatMap((s) => s.split(" ")) turns sentences into one list of words. It only flattens one level.

  6. 6

    Transposing a grid (rows become columns) is a classic exercise: grid[0].map((_, c) => grid.map((row) => row[c])). And grids are jagged in JavaScript if you want: rows can have different lengths, so don't assume grid[0].length fits every row unless you built it that way.

Explain it without notes

01

How do you read and loop over a 2D array?

02

Why does new Array(3).fill([]) cause bugs, and how do you build a grid correctly?

03

What is the difference between flat and flatMap?

04

How would you transpose a matrix in JavaScript?

Practice

01

Create a 3×3 multiplication table as a 2D array (table[r][c] is (r + 1) * (c + 1)) and print the middle row.

02

Given const orders = [["pen", "cap"], ["bag"], []];, make one list of all items and count them.

Trade-offs

  • ↔

    Arrays of arrays are easy to read and write (grid[r][c]); a flat typed array is faster for big numeric data but needs index arithmetic.

  • ↔

    flat(Infinity) is convenient but hides how deep your data really is; if you know the shape, flatten to that exact depth.

Done when you can

  • I can create a 2D array and read any cell with grid[r][c].

  • I can loop over every cell with nested loops.

  • I can build an empty grid without the shared-row fill bug.

  • I can flatten arrays with flat and a depth.

  • I can use flatMap to map and flatten one level.