Flattening a nested array recursively, how does this work?

Here’s the link to the challenge https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/steamroller
The base case appears to be Array.isArray(arr) but since that is always true, how exactly does this work?

function steamrollArray(arr) {
    return Array.isArray(arr) ? [].concat(...arr.map(steamrollArray)) : arr;
}

console.log(steamrollArray([1, [2], [3, [[4]]]]));

The base case (when recursion stops) is when arr is not an array.

But isn’t it always an array? The final flattened array is an array too, isn’t it?

For the input [1, [2], [3, [[4]]]] we end up calling map on the array, so steamrollArray is going to be called for each item in the outermost array, those items are 1, [2], and [3, [[4]]]. So, 1 is not an array.

So it checks the condition Array.isArray(arr) for every element in arr? But in that case on the first call it would return [false, true, true], right? And only the true values would be concatenated, excluding 1. What am I missing here?

In this case Array.isArray(arr) is the condition in a ternary operator. Its not checking every element in arr, just that arr is an array.

If its true it returns [].concat(...arr.map(steamrollArray)) else it returns arr.

But arr is always an array so how come the function stops ? I’m very familiar with the ternary operator, I’m only confused about the logic of the code.

arr is eventually going to be something else other then an array.
Watch it run.

It’s not clear what’s happening at each step. If anything it makes it more confusing. I solved the problem using my own recursive function so I do have a grasp of the concept, but this particular one is a mystery to me.

Yeah its annoying to read for me personally as well, the spread operator is throwing me off, but im also relatively new at this. But the .map is going over arr, and the function given to map is steamrollArray. So 1 is not an array and returns 1 and gets stored in the return array of map, [2] is an array and gets tossed back into steamrollArray.

So in a new steamrollArray [2] is still an array so it gets tossed into map. map looks at it with the steamrollArray function and says 2 is not an array and returns it, then stored in this maps return array, spread and concatenated to an empty array, and returned back to the original map as [2] i believe.

Then [3,[4]] is an array so that gets tossed back into steamrollArray, and [3,[4]] is an array so it gets looked at by map. map looks at 3 and says its not an array and returns it to this map’s array. It then looks at [4] and says its an array so we toss it in steamrollArray. map then looks at 4 and sees its not an array so it gets returned, then stored this maps array, spread and concatenated to an empty array, and returned back to this new map as [3,[4]] i believe. It then gets spread and concatenated into an empty array and returned as [3,4] to the original map array.

So now the original map has [1,[2],[3,4]]. And because of the spread and concat with [1,[2],[3,4]] it becomes [1,2,3,4] and is returned out of the function. Someone who knows more will come along and correct me if im wrong or explain it better.

Can anybody else chime in on this?

function steamrollArray(arr) {
    return Array.isArray(arr) ? [].concat(...arr.map(elem => SteamrollArray(elem))) : arr;
}

Expanding the callback for the map might help.

You recursively steamroll elements of the array.

So effectively it is checking Array.isArray(arr) for every element in the array and when none is an array, it returns arr?

1 Like

Mostly. If the argument is an array, then it recursively calls steamroll on each item in the array. Any sub arrays are also steamrolled while individual elements are just returned.

But arr is always an array, that is what I’m confused about. For example this would make more sense to me but it doesn’t work

function steamrollArray(arr) {
    return arr.some(a => Array.isArray(a)) ? [].concat(...arr.map(steamrollArray)) : arr;
}

Is every individual element always an array?

No, but I thought this was the condition being checked Array.isArray(arr) and
arr.map(elem => SteamrollArray(elem) is executed if the condition is true, else return arr. Isn’t that so?

Right, but the items of the array are being passed back to a new call to the function… Those individual items are being checked in the recursive call to see if they are arrays or not.

1 Like

Okay, I understand now. I created this “simulation” which helped me see what was going on during recursion.

let nums = [1, [2, [3]]]
function add(n) {
    if (Array.isArray(n)) {
      let r = [].concat(n)
    return r  
    } else {
        return n
    }
}
let flattened1 = [].concat(...nums.map(add))
console.log(flattened1)
let flattened2 = [].concat(...flattened1.map(add))
console.log(flattened2)
1 Like