0

Code shows a recursive function that takes a number, say n=5, and returns an array counting down from n to 1 i.e [5,4,3,2,1].

My confusion lies right before we push the numbers/values of n to countArray. My understanding is that countup(n - 1), will generate numbers (say n=5) 5,4,3,2,1...but I don't understand where/how they are stored. I would have thought n would end up as its last defined value, n=1, or a blank array. But that's not true and they are somehow all pushed into an array that in my understanding was never created/defined prior to pushing into it. So those two lines with comments are the two I need help understanding.

tl;dr: (1) how are the values 5->1 stored without overwriting to the final value 1, prior to being pushed into the array? (2) Where/how was countArray defined as an array before we push into it;

function countup(n) {
  if (n < 1) {
    return [];
  } else {
    const countArray = countup(n - 1);  //the storing of 5,4,3,2,1 I don't understand
    countArray.push(n); //I don't understand when countArray was defined as an array
    return countArray;
  }
}
console.log(countup(5)); // [ 1, 2, 3, 4, 5 ]

edit: this post's title probably need changing to array rather than variable or etc.

evolutionxbox
  • 3,932
  • 6
  • 34
  • 51
kite
  • 309
  • 2
  • 12
  • `return [];` is where it was declared as an array. – Heretic Monkey May 04 '20 at 19:41
  • The numbers are generated and then the array is declared, how do the numbers get into the array if after the fact tho? – kite May 04 '20 at 19:42
  • What do you mean "after the fact"? Follow how the function works, line by line, and you will find that `return [];` is where the array is defined and returned. This happens _before_ the push. – evolutionxbox May 04 '20 at 19:43
  • I mean the else statement runs and generates a 5...4...3...2...1.. then an array. I don't get how they can be put into the array if the array is defined after all those numbers are. – kite May 04 '20 at 19:44
  • As @HereticMonkey said, the array happens first – evolutionxbox May 04 '20 at 19:44
  • Okay, then I don't understand why that happens first then. I might need a walkthrough of whats happening in full. – kite May 04 '20 at 19:45
  • Please use a debugger to step through the code line by line; it will tell you exactly when the values are pushed to the array. – Heretic Monkey May 04 '20 at 19:45
  • Does this answer your question? [Javascript Recursion?](https://stackoverflow.com/questions/37306467/javascript-recursion) – evolutionxbox May 04 '20 at 19:46
  • 1
    Does this answer your question? [Understanding how recursive functions work](https://stackoverflow.com/questions/25676961/understanding-how-recursive-functions-work) – Heretic Monkey May 04 '20 at 19:48

5 Answers5

3

Perhaps adding some logging might make it more clear.

We can add a simple logger that reports the values like this:

Calling with 5
|   Calling with 4
|   |   Calling with 3
|   |   |   Calling with 2
|   |   |   |   Calling with 1
|   |   |   |   |   Calling with 0
|   |   |   |   |   Returning [] (base case)
|   |   |   |   Pushing 1 to []
|   |   |   |   Returning [1]
|   |   |   Pushing 2 to [1]
|   |   |   Returning [1,2]
|   |   Pushing 3 to [1,2]
|   |   Returning [1,2,3]
|   Pushing 4 to [1,2,3]
|   Returning [1,2,3,4]
Pushing 5 to [1,2,3,4]
Returning [1,2,3,4,5]

So the array was defined in the base case. Then as we worked our way back up the call stack, we added to it. There are alternative ways of doing this, but this is one common and reasonable way to go about it.

You can see how I added the logging in the following snippet:

const log = (depth, message) => 
  console .log ('|   '.repeat (depth - 1)  + message)


function countup(n, depth = 1) {
  log(depth, `Calling with ${n}`)
  if (n < 1) {
    log(depth, `Returning [] (base case)`)
    return [];
  } else {
    const countArray = countup(n - 1, depth + 1);  //the storing of 5,4,3,2,1 I don't understand
    log(depth, `Pushing ${n} to [${countArray}]`)
    countArray.push(n); //I don't understand when countArray was defined as an array
    log(depth, `Returning [${countArray}]`)
    return countArray;
  }
}

countup(5)
.as-console-wrapper {min-height: 100% !important; top: 0}

Update

Perhaps clearer would be this result:

/ Calling with 5
|   / Calling with 4
|   |   / Calling with 3
|   |   |   / Calling with 2
|   |   |   |   / Calling with 1
|   |   |   |   |   / Calling with 0
|   |   |   |   |   \ Returning [] (base case)
|   |   |   |   | Pushing 1 to []
|   |   |   |   \ Returning [1]
|   |   |   | Pushing 2 to [1]
|   |   |   \ Returning [1,2]
|   |   | Pushing 3 to [1,2]
|   |   \ Returning [1,2,3]
|   | Pushing 4 to [1,2,3]
|   \ Returning [1,2,3,4]
| Pushing 5 to [1,2,3,4]
\ Returning [1,2,3,4,5]

Which only involves a minor change to the logging statements:

const log = (depth, message) => 
  console .log ('|   '.repeat (depth - 1)  + message)
  
function countup(n, depth = 1) {
  log(depth, `/ Calling with ${n}`)
  if (n < 1) {
    log(depth, `\\ Returning [] (base case)`)
    return [];
  } else {
    const countArray = countup(n - 1, depth + 1);  //the storing of 5,4,3,2,1 I don't understand
    log(depth, `| Pushing ${n} to [${countArray}]`)
    countArray.push(n); //I don't understand when countArray was defined as an array
    log(depth, `\\ Returning [${countArray}]`)
    return countArray;
  }
}

countup(5)
.as-console-wrapper {min-height: 100% !important; top: 0}
Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
1

Every recursive function, that ends at least, will have a stop condition.

For your function that is

if (n < 1) {
 return [];
}

The other part of a recursive function is the actual recursion.

This happens here

const countArray = countup(n - 1);

You are calling the function with an n that is one less.

You'll hit that branch and trigger the recursion until you reach <1. At that point the array is created and it gets returned.

After that you get to push values in that array.

It is very important that you return countArray;, this way the array is pushed to the calling functions.

Most likely what you're missing is that when a function is called, the calling function waits for it to end and then goes further.

Usually you understand recursive functions if you try to map the stack and map all the calls to the functions.

Radu Diță
  • 13,476
  • 2
  • 30
  • 34
1

(I know this should be a comment, but I can't add code in the comment section).

I think the best way to understand where the array is created, is following the flow yourself. It would make you understand better than all the words that an explanation could be made of.

Just do the following steps:

  1. Open the console in your browser. Press F12 then click the console tab
  2. Paste the code with a debugger statement:

-

function countup(n) {
  debugger;
  if (n < 1) {
    return [];
  } else {
    const countArray = countup(n - 1);  //the storing of 5,4,3,2,1 I don't understand
    countArray.push(n); //I don't understand when countArray was defined as an array
    return countArray;
  }
}
console.log(countup(5)); // [ 1, 2, 3, 4, 5 ]
  1. Use the step in and step over buttons
  2. Have fun!
0

TL;DR

It is defined in return [];.

The parameter (n) decrements every time you enter the function and the array will be created if it reaches 0.


Let's say, you call countup(2).

This will reach the else that will call countup(1). That process repeats and calls countup(0).

This does not go in the else branch and instead creates and returns an empty array.

The calling method, countup(1) adds 1 to it (and returns the result) and it's caller countup(2) adds 2.

I will try to visualise the recursion:

countup(2)
  2<1 --> false --> else
  countArray=countup(1)
    1<1 --> false --> else
      countArray=countup(0)
        0<1 --> true --> if
        return []
      push 1 --> [1]
      return countArray --> [1]
  push 1 -->[1,2]
  return countArray -->[1,2]
dan1st
  • 12,568
  • 8
  • 34
  • 67
0

At this point

const countArray = countup(n - 1);

the function "waits" and provokes a call to itself, which will stop only at the stop condition if (n < 1) at countup(0), which returns an empty array and returns to the preceding call of countup(1). At this point countArray gets the value returned from countup(0) which is [] and it is declared as an array, which also makes future push calls valid.

The variables are meanwhile stored in memory. When calling a function, it's parameters, along with it's local variables and return address are stored in the memory stack.

You might want to read more about recursion and call stack.