8.7 KiB
id, title, challengeType, dashedName
| id | title | challengeType | dashedName |
|---|---|---|---|
| 6733b02d1e556005a544c5e3 | What Is Recursion, and How Does It Work? | 19 | what-is-recursion-and-how-does-it-work |
--interactive--
Recursion is a complicated feature that allows you to call a function repeatedly until a base-case is reached. Unlike a traditional loop, recursion allows you to handle something with an unknown depth, such as deeply nested objects/arrays, or a file tree. But you can also use it for more basic tasks, such as counting down from a given number.
Let's construct a function to do exactly that. We’ll call our function recursiveCountdown, and it needs to accept a number. We’ll have it print this number to the console:
:::interactive_editor
const recursiveCountdown = (number) => {
console.log(number);
};
recursiveCountdown(5);
:::
Now if we call this and pass the number 5, we’ll see the number print to our terminal. But nothing else happens – and the number 5 certainly isn’t a countdown.
Before we start building the recursive portion of our function, we need to establish our base case first. If you don’t have a base case established, your code will run until it exceeds your memory allocation and crashes.
:::interactive_editor
const recursiveCountdown = (number) => {
if (number < 1) {
return;
}
console.log(number);
};
recursiveCountdown(5);
:::
For our base case, we want the countdown to stop if the number is less than 1. When we hit that base-case, we can return to break out of the function execution.
Now that we’ve safely prepared a base-case, we can set up the recursion. The key point that makes a function recursive is that it calls itself in its execution. In this case, we want to call the function after we print the number. But in order to count down, our new number needs to be one less:
:::interactive_editor
const recursiveCountdown = (number) => {
if (number < 1) {
return;
}
console.log(number);
recursiveCountdown(number - 1);
};
recursiveCountdown(5);
:::
This would log the numbers 5, 4, 3, 2, and 1 to the console.
We do get our five numbers! But what if we wanted to count up instead? Rather than writing an entirely new function, we can swap the order of our log and our recursive call:
:::interactive_editor
const recursiveCountdown = (number) => {
if (number < 1) {
return;
}
recursiveCountdown(number - 1);
console.log(number);
};
recursiveCountdown(5);
:::
This would log the numbers 1, 2, 3, 4, and 5 to the console.
But why does that work? Well, to understand this you need to understand the call stack. The call stack is how JavaScript tracks and resolves function calls. The stack functions as a last-in-first-out queue of sorts. To understand this better, let’s add some logging to our function:
:::interactive_editor
const recursiveCountdown = (number) => {
console.log(`Function execution started for number: ${number}`);
if (number < 1) {
console.log(`Base case reached, begin resolving stack`);
return;
}
console.log(`Calling recursiveCountdown with number: ${number - 1}`);
recursiveCountdown(number - 1);
console.log(`Function execution completed for number: ${number}`);
};
recursiveCountdown(5);
:::
We’ve added four key statements here. The first log runs when a function call begins executing. The third log runs just before the recursive function is called. And the fourth log runs when the function execution has ended. The result is:
Function execution started for number: 5
Calling recursiveCountdown with number: 4
Function execution started for number: 4
Calling recursiveCountdown with number: 3
Function execution started for number: 3
Calling recursiveCountdown with number: 2
Function execution started for number: 2
Calling recursiveCountdown with number: 1
Function execution started for number: 1
Calling recursiveCountdown with number: 0
Function execution started for number: 0
Base case reached, begin resolving stack
Function execution completed for number: 1
Function execution completed for number: 2
Function execution completed for number: 3
Function execution completed for number: 4
Function execution completed for number: 5
But how does this happen? Well, this is where the call stack comes in to play. When we call recursiveCountdown(5), that function call is added to the call stack.
When that function call reaches the point where it needs to call recursiveCountdown(4), it has to stop and wait for that result. Meanwhile, our recursiveCountdown(4) gets added to the call stack, on top of the recursiveCountdown(5).
When that function call reaches the point where it needs to call recursiveCountdown(3), it has to stop and wait for that result. Meanwhile, our recursiveCountdown(3) gets added to the call stack, on top of the recursiveCountdown(4).
When that function call reaches the point where it needs to call recursiveCountdown(2), it has to stop and wait for that result. Meanwhile, our recursiveCountdown(2) gets added to the call stack, on top of the recursiveCountdown(3).
When that function call reaches the point where it needs to call recursiveCountdown(1), it has to stop and wait for that result. Meanwhile, our recursiveCountdown(1) gets added to the call stack, on top of the recursiveCountdown(2).
And finally, when that function call reaches the point where it needs to call recursiveCountdown(0), it has to stop and wait for that result. Meanwhile, our recursiveCountdown(0) gets added to the call stack, on top of the recursiveCountdown(1).
But recursiveCountdown(0) doesn’t call another function – it hits our base case, where it returns early. Because the execution of that function has ended, the function call can be considered “resolved”. When the call is resolved, it’s removed from the stack.
Now our recursiveCountdown(1) is no longer waiting on that call – it’s at the top of the stack and can resume executing.
recursiveCountdown(1) resolves, is removed from the stack, and allows recursiveCountdown(2) to resume execution.
recursiveCountdown(2) resolves, is removed from the stack, and allows recursiveCountdown(3) to resume execution.
recursiveCountdown(3) resolves, is removed from the stack, and allows recursiveCountdown(4) to resume execution.
recursiveCountdown(4) resolves, is removed from the stack, and allows recursiveCountdown(5) to resume execution.
And recursiveCountdown(5) resolves and is removed from the stack. Our call stack is now empty, so the recursion is complete!
This is a basic overview of how recursion works in JavaScript. It’s a complicated concept, and you should play around with some code and log statements until you’re comfortable with the behavior of the call stack.
For a little fun fact, we talked about how a lack of a base-case can cause your code to crash when it runs out of memory. This is because the recursion keeps piling more and more function calls into the call stack until the stack overflows. Just like the name of the popular programming community.
--questions--
--text--
What is the primary purpose of a base case in a recursive function?
--answers--
To initiate the recursive calls.
--feedback--
The lesson mentions what happens if you don't have a base case established.
To prevent infinite recursion and stack overflow.
To improve performance of the function.
--feedback--
The lesson mentions what happens if you don't have a base case established.
To handle error conditions.
--feedback--
The lesson mentions what happens if you don't have a base case established.
--video-solution--
2
--text--
In the context of recursion, what is the call stack used for?
--answers--
To store local variables.
--feedback--
The lesson explains how the call stack manages function calls during recursion.
To track and resolve function calls.
To optimize recursive functions.
--feedback--
The lesson explains how the call stack manages function calls during recursion.
To handle asynchronous operations.
--feedback--
The lesson explains how the call stack manages function calls during recursion.
--video-solution--
2
--text--
What happens when a recursive function call reaches its base case?
--answers--
It starts a new recursive cycle.
--feedback--
Think about how the function behaves when it hits the condition that stops the recursion.
It throws an error.
--feedback--
Think about how the function behaves when it hits the condition that stops the recursion.
It returns and allows the previous call to resume execution.
It continues to call itself indefinitely.
--feedback--
Think about how the function behaves when it hits the condition that stops the recursion.
--video-solution--
3