My code works, but I don't understand the logic behind it

Maybe someone can help me understand this. I got the code below to work just by trying different things until it took, but I’m confused by why it works.

It seems to me that if I pass the function the argument of 5, it would process that argument first, and it would become the first item in the countArray constant. Therefore the next iteration would be 4, and then 3., and then so on to 1. What confuses me is this: if I’m adding the result of each subsequent iteration to the front of the array using .unshift()… shouldn’t countArray resolve to [1,2,3,4,5] at the end?

ƒ countDown(n) {
    if (n < 1) {
        return [];
    } else {
        const countArray = countDown(n - 1);
        countArray.unshift(n);
        return countArray;
    }
}
    
countDown(5) 
[5,4,3,2,1]

Challenge: Use Recursion to Create a Countdown

Link to the challenge:

It helps me to mentally replace the function call with its result, so

        const countArray = countDown(n - 1);
        countArray.unshift(n);
        return countArray;

looks like

        const countArray = [n - 1, n - 2, n - 3, ... 1];
        countArray.unshift(n);
        return countArray;

This in turn becomes

        const countArray = [n - 1, n - 2, n - 3, ... 1];
        countArray = [n, n - 1, n - 2, n - 3, ... 1];
        return countArray;

Thanks for the quick response, Jeremy!

I’m still a little confused on how the contents of countArray get reversed, though. If the recursion is counting down from 5 and resolving the arguments one after the other, and then .unshift is adding the result of each iteration to the front of the array (so in front of the last iteration), wouldn’t:

const countArray = [n - 1, n - 2, n - 3, … 1];

Equal:

const countArray = [1,2,3,4]; ?

Recursion is tricky and it can be hard to see the order of operations.

Every single function call is a separate instance, and each function call does not return until the recursive function call with the reduced input returns.

So in the case of countDown(5) we have

  • countDown(5) needs countDown(4)
  • countDown(4) needs countDown(3)
  • countDown(3) needs countDown(2)
  • countDown(2) needs countDown(1)
  • countDown(1) needs countDown(0)
  • countDown(0) returns []
  • countDown(1) returns [].unshift(1), which is [1]
  • countDown(2) returns [1].unshift(2), which is [2, 1]
  • countDown(3) returns [2, 1].unshift(3), which is [3, 2, 1]
  • countDown(4) returns [3, 2, 1].unshift(4), which is [4, 3, 2, 1]
  • countDown(5) returns [4, 3, 2, 1].unshift(4), which is [5, 4, 3, 2, 1]
2 Likes

To piggyback off of @JeremyLT , what he wrote out is referred to as a “call stack”. It’s very useful to write it out when you’re doing recursion. If you console.log it out each time, it’ll look like what he’s talking about, not just for this question, but for all recursion problems.

1 Like

This is really helpful. Thanks so much for taking the time to explain it!

1 Like

you can see the call stack in the console using debugger; statement to stop the code execution at any time.
Sooner or later you will have to use debugger; and the browser’s developer tools.
Breakpoints are a must for any developer.

1 Like

I found it hard to understand recursion at first. But the help of the dev tools, as @GeorgeCrisan suggest, i understood the logic - i don’t use it that much anyway