Creating Multidimensional Arrays in Javascript

Initialising multidimensional arrays in Javascript can be tricky because some initialisation approches lead to unexpected behaviour due to shallow copying.

2D Arrays (Matrices)

If we try to create a 4 \(\times\) 4 matrix by filling an empty array with 4 zero-filled arrays each with 4 zeros using the Array(n).fill(e) syntax:

let arr = Array(4).fill(Array(4).fill(0));
console.table(arr);
┌─────────┬───┬───┬───┬───┐
│ (index) │ 0 │ 1 │ 2 │ 3 │
├─────────┼───┼───┼───┼───┤
│    0    │ 0 │ 0 │ 0 │ 0 │
│    1    │ 0 │ 0 │ 0 │ 0 │
│    2    │ 0 │ 0 │ 0 │ 0 │
│    3    │ 0 │ 0 │ 0 │ 0 │
└─────────┴───┴───┴───┴───┘

and then try to change the element \(arr_{1,2}\):

arr[1][2] = 1;
console.table(arr);
┌─────────┬───┬───┬───┬───┐
│ (index) │ 0 │ 1 │ 2 │ 3 │
├─────────┼───┼───┼───┼───┤
│    0    │ 0 │ 0 │ 1 │ 0 │
│    1    │ 0 │ 0 │ 1 │ 0 │
│    2    │ 0 │ 0 │ 1 │ 0 │
│    3    │ 0 │ 0 │ 1 │ 0 │
└─────────┴───┴───┴───┴───┘

All elements in column 2 (arr[0:3][2]) get mutated, which hardly would be what you want. It appears that the fill() method used shallow copies of [...Array(4).fill(0)] to fill the outer array.

A straightforward workaround is to serialise and de-serialise using JSON.stringify() and JSON.parse():

let arr = Array(4).fill(Array(4).fill(0));
arr = JSON.parse(JSON.stringify(arr));
arr[1][2] = 1;
console.table(arr);
┌─────────┬───┬───┬───┬───┐
│ (index) │ 0 │ 1 │ 2 │ 3 │
├─────────┼───┼───┼───┼───┤
│    0    │ 0 │ 0 │ 0 │ 0 │
│    1    │ 0 │ 0 │ 1 │ 0 │
│    2    │ 0 │ 0 │ 0 │ 0 │
│    3    │ 0 │ 0 │ 0 │ 0 │
└─────────┴───┴───┴───┴───┘

Now, only the single element arr[1][2] gets mutated.

We can also use the following syntax to directly initialise a matrix whose elements can be properly mutated:

let arr = Array.from({ length: 4}, e => Array(4).fill(0));
arr[1][2] = 1;
console.table(arr);
┌─────────┬───┬───┬───┬───┐
│ (index) │ 0 │ 1 │ 2 │ 3 │
├─────────┼───┼───┼───┼───┤
│    0    │ 0 │ 0 │ 0 │ 0 │
│    1    │ 0 │ 0 │ 1 │ 0 │
│    2    │ 0 │ 0 │ 0 │ 0 │
│    3    │ 0 │ 0 │ 0 │ 0 │
└─────────┴───┴───┴───┴───┘

Higher Dimensional Arrays

The shallow-copying problem related to Array(n).fill(e) chaining also happens in 3D arrays:

let arr = Array(3).fill(Array(3).fill(Array(3).fill(0)));
arr[1][1][2] = 1;
console.table(arr);
┌─────────┬─────────────┬─────────────┬─────────────┐
│ (index) │      0      │      1      │      2      │
├─────────┼─────────────┼─────────────┼─────────────┤
│    0    │ [ 0, 0, 1 ] │ [ 0, 0, 1 ] │ [ 0, 0, 1 ] │
│    1    │ [ 0, 0, 1 ] │ [ 0, 0, 1 ] │ [ 0, 0, 1 ] │
│    2    │ [ 0, 0, 1 ] │ [ 0, 0, 1 ] │ [ 0, 0, 1 ] │
└─────────┴─────────────┴─────────────┴─────────────┘

The same methods (JSON.parse(JSON.stringify(arr)) and Array.from()) can be used to properly initialising them.

let arr = Array.from(
  { length: 3 }, e => Array.from(
    { length: 3 }, e => Array(3).fill(0)));
arr[1][1][2] = 1;
console.table(arr);
┌─────────┬─────────────┬─────────────┬─────────────┐
│ (index) │      0      │      1      │      2      │
├─────────┼─────────────┼─────────────┼─────────────┤
│    0    │ [ 0, 0, 0 ] │ [ 0, 0, 0 ] │ [ 0, 0, 0 ] │
│    1    │ [ 0, 0, 0 ] │ [ 0, 0, 1 ] │ [ 0, 0, 0 ] │
│    2    │ [ 0, 0, 0 ] │ [ 0, 0, 0 ] │ [ 0, 0, 0 ] │
└─────────┴─────────────┴─────────────┴─────────────┘