A Cautionary Tale on Using JavaScript's fill() Method
Tags: javascript, array, algorithms
Published: 2020-06-20 16:12:34

As part of ES6 features, the [Array.prototype.fill()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill) method allows us to add, replace, or generate new element(s) in an array.

To me, it is a clever alternative to plain-old for loops when it comes to populating the same elements inside an array. 

For instance, if you want to create an array that has three of the same elements, instead of creating an empty array and pushing each new element into the array via for loops, this one-liner will do the trick:
```js
const threeFives = Array(3).fill(5)
threeFives
// [5, 5, 5]
```

Now, let's try implementing the `fill()` method in this classic spiral matrix algorithm challenge.
```js
/* Direction:
Write a function that accepts an integer N, and returns a NxN spiral matrix. */

// Examples: 

matrix(2)
// [[1, 2]
// [4, 3]]

matrix(3)
// [[1, 2, 3],
//  [8, 9, 4],
//  [7, 6, 5]]

matrix(4)
// [[1,   2,  3, 4],
//  [12, 13, 14, 5],
//  [11, 16, 15, 6],
//  [10,  9,  8, 7]]
```

If you haven't worked on this challenge, I encourage you to give it a try first before reading further.

Most of the solutions I've found start off by populating an empty array of empty subarrays, like this one:
```js
function matrix(n) {
    const results = []
    for (let i = 0; i < n; i++) {
        results.push([])
    }

    let counter = 1
    let startColumn = 0
    let endColumn = n - 1
    let startRow = 0
    let endRow = n - 1
    while (startColumn <= endColumn && startRow <= endRow){
        // top row
        for (let i = startColumn; i <= endColumn; i++) {
            results[startRow][i] = counter
            counter++
        }
        startRow++
        // right column
        for (let i = startRow; i <= endRow; i++) {
            results[i][endColumn] = counter
            counter++
        }
        endColumn--
        // bottom row
        for (let i = endColumn; i >= startColumn; i--) {
            results[endRow][i] = counter
            counter++
        }
        endRow--
        // start column
        for (let i = endRow; i >= startRow; i--) {
            results[i][startColumn] = counter
            counter++
        }
        startColumn++
    }
    return results
}
```

As mentioned earlier, we can use `fill()` in place of the for loop in the first three lines. So, instead of:
```js
    const results = []
    for (let i = 0; i < n; i++) {
        results.push([])
    }
```

We could use `fill()` like so:
```js
const results = Array(n).fill([])
```

At this point, when you log the `results` in your console, you can see that they produce the same output.

Cool, so if we keep the same logic in the rest of the solution, we'll see the same result, right?

Wrong.

According to the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill), if the first parameter of the `fill()` method is an object, "each slot in the array will reference that object."

![MDN Array.fill screenshot](https://dev-to-uploads.s3.amazonaws.com/i/6hkitejvxmiy9hiw4tzk.png)

And this is where using `fill()` might start messing up your beautifully-crafted matrix function.

As illustrated in the same MDN documentation, if you pass in an object and mutate the items inside one object, all of the subsequent objects in the array will be filled with the same items.
```js
let arr = Array(3).fill({}) // [{}, {}, {}]
arr[0].hi = "hi"            // [{ hi: "hi" }, { hi: "hi" }, { hi: "hi" }]
```

Similarly, in the matrix function, all subarrays we created via `fill()` will change even though we only want to change one subarray.

Here I console-logged the results of each iteration in the main matrix production. As you can see below, all subarrays created with `fill([])` keep mirroring the mirroring the first subarray:

```
using for loop:  [ [ 1 ], [] ]
using fill([]):  [ [ 1 ], [ 1 ] ]
=====
using for loop:  [ [ 1, 2 ], [] ]
using fill([]):  [ [ 1, 2 ], [ 1, 2 ] ]
=====
using for loop:  [ [ 1, 2 ], [ <1 empty item>, 3 ] ]
using fill([]):  [ [ 1, 3 ], [ 1, 3 ] ]
=====
using for loop:  [ [ 1, 2 ], [ 4, 3 ] ]
using fill([]):  [ [ 4, 3 ], [ 4, 3 ] ]
=====
```

Does it mean we shouldn't use `fill()` to create an array of empty subarrays?

Here's a solution I found that appears to successfully produce array placeholders without side effects:
```js
const results = Array(n).fill().map(()=>Array(n).fill());
```

Instead of filling the array with arrays, which are object types, we simply fill it with nothingness. In JavaScript, the nothingness is `undefined`, which is NOT an object, hence allowing us to reassign new items in each iteration without affecting other subarrays.

You can see the distinction by comparing all three usages of generating arrays and subarrays:
```
using for loop:  [ [ 1 ], [] ]
using fill([]):  [ [ 1 ], [ 1 ] ]
using fill().map():  [ [ 1, undefined ], [ undefined, undefined ] ]
=====
using for loop:  [ [ 1, 2 ], [] ]
using fill([]):  [ [ 1, 2 ], [ 1, 2 ] ]
using fill().map():  [ [ 1, 2 ], [ undefined, undefined ] ]
=====
using for loop:  [ [ 1, 2 ], [ <1 empty item>, 3 ] ]
using fill([]):  [ [ 1, 3 ], [ 1, 3 ] ]
using fill().map():  [ [ 1, 2 ], [ undefined, 3 ] ]
=====
using for loop:  [ [ 1, 2 ], [ 4, 3 ] ]
using fill([]):  [ [ 4, 3 ], [ 4, 3 ] ]
using fill().map():  [ [ 1, 2 ], [ 4, 3 ] ]
=====
```

Next time, when you are tempted to use `fill()` method, be sure to check the type of value you are passing in. It was a hard lesson I learned by *almost* cracking the spiral matrix code challenge on second try.