This repo requires each function call itself recursively and pays no attention to whether inner recursive functions are defined and called.
While both are valid uses of recursion, there are important lessons to learn by following the method this repo enforces. Defining inner functions and calling them recursively relies on side effects, while following the more pure approach requires an understanding of how values are passed through the call stack.
This repo restricts expanding the number of parameters a function accepts.
Expanding the number of parameters is a valid approach, but has been restricted here to emphasize certain lessons while learning recursion.
Watch
, Star
, and Fork
this repo. You know you want to.
SpecRunner.html
in your web browserrecursion.js
spec/part1.js
and spec/part2.js
as necessaryRecursion is when a function calls itself until it doesn’t. –not helpful person
Is it a true definition? Mostly. Recursion is when a function calls itself. A recursive function can call itself forever, but that’s generally not preferred. It’s often a good idea to include a condition in the function definition that allows it to stop calling itself. This condition is referred to as a base case. As a general rule, recursion shouldn’t be utilized without an accompanying base case unless an infinite operation is desired. This leaves us with two fundamental conditions every recursive function should include: - A base
case - A recursive
case
What does this all mean? Let’s consider a silly example:
function stepsToZero(n) {
if (n === 0) { /* base case */
return 'Reached zero';
} else { /* recursive case */
console.log(n + ' is not zero');
return stepsToZero(n-1);
}
}
This function doesn’t do anything meaningful, but hopefully it demonstrates the fundamental idea behind recursion. Simply put, recursion provides us a looping or repeating mechanism. It repeats an operation until a base
condition is met. Let’s step through an invocation of the above function to see how it evaluates.
stepsToZero(n)
where n
is the number 2
Invoke stepsToZero(n-1)
where n-1
evaluates to 1
Every recursive call adds a new invocation to the stack on top of the previous invocation
stepsToZero(n-1)
where n-1
evaluates to 0
Return out of the initial invocation
Note that the value returned from the base case (step 9) gets returned to the previous invocation (step 4) on the stack. Step 4’s invocation takes that value and returns it to the invocation that preceded it (step 1). Once the initial invocation is reached, it returns the value to whatever invoked it. Through these steps, you can watch the call stack build up and once the base case is reached, the return value is passed back down as each invocation pops off the stack.
Due to the way the execution stack operates, it’s as if each function invocation pauses in time when a recursive call is made. The function that pauses before a recursive call will resume once the recursive call completes. If you’ve seen the movie [Inception], this model may sound reminiscent to when the characters enter a person’s dreams and time slowed. The difference is time doesn’t actually slow with recursive invocations; rather, it’s a matter of order of operations. If a new invocation enters the execution stack, that invocation must complete before the previous can continue and complete.
Recursion can be elegant, but it can also be dangerous. In some cases, recursion feels like a more natural and readable solution; in others, it ends up being contrived. In most cases, recursion can be avoided entirely and sometimes should in order to minimize the possibility of exceeding the call stack and crashing your app. But keep in mind that code readability is important. If a recursive solution reads more naturally, then it may be the best solution for the given problem.
Recursion isn’t unique to any one programming language. As a software engineer, you will encounter recursion and it’s important to understand what’s happening and how to work with it. It’s also important to understand why someone might use it. Recursion is often used when the depth of a thing is unknown or every element of a thing needs to be touched. For example, you might use recursion if you want to find all DOM elements with a specific class name. You may not know how deep the DOM goes and need to touch every element so that none are missed. The same can be said for traversing any structure where all possible paths need to be considered and investigated.
Recursion is often used in divide and conquer algorithms where problems can be divided into similar subproblems and conquered individually. Consider traversing a tree structure. Each branch may have its own “children” branches. Every branch is essentially just another tree which means, as long as child trees are found, we can recurse on each child.