Demystifying Recursion: A Beginner's Guide to Understanding Recursive Functions in JavaScript

Bimbo AdesoyeBimbo Adesoye
8 min read

A recursive function, simply put is a function that calls itself inside its function block (The function block is the body of the function). It's that straightforward. However, it remains one of the most confusing topics in programming.

In this article, I will simplify recursion with some examples, highlight how it differs from an iterative function and help you determine when and how to use a recursive function.

Anatomy of a Recursive Function

A recursive function is defined by 3 elements :

  • The function declaration/definition

  • The base condition and;

  • The recursive call

The function declaration :

function countDownRecursive() {

}

The base condition: The base condition is important to prevent infinite recursion. Basically, it indicates when to exit the recursive call.

function countDownRecursive() {
if(the base condition) // The base condition
}

The recursive call :

function countDownRecursive() {
if(the base condition) {

}
countDownRecursive() // The recursive call
}

These three core elements make up the structure of a recursive function.

We will now be going into examples, where I'll be converting Iterative functions to recursive functions. This is because understanding the same piece of code written with an iterative function will make it easier to understand/connect how the recursive function works.

Example 1: A Countdown Function

function countDown(num) {
  for (let i = num; i > 0; i--) {
    console.log(i);
  }
  console.log("Hooray");
}

countDown(3);

This is a countdown function written with a regular for loop.

As you can see below if you run this code in your console, "3, 2, 1, 'Hooray'" will be printed.

This is a simple enough function that prints 'Hooray' after we've looped through a set of numbers. Now, let's write this with a recursive function;

function countDownRecursive(n) {
  if (n <= 0) {
    console.log("Hooray");
    return;
  }
  console.log(n);
  countDownRecursive(n - 1);
}
countDownRecursive(3);

At first glance, this might seem a little bit complicated but this is how it works:

We declare our function "countDownRecursive()" and pass a parameter "n" which we'll be replacing with an argument when we call our function. We then put in our base condition (the if statement). In the iterative function where we used a for loop, the stopping condition was i > 0 because we broke out of the loop if "i" was less than or equal to zero. So in our recursive function, our base condition is n <= 0 and then we break out of our function using the return keyword. However, we first log 'Hooray' beforehand because that's the result we want at the end of our function.

In the next step, we log n to the console and then call the function passing in n - 1 as the parameter. This reduces the value of n after each call.

When we call our function with the number 3, as in the example, it goes through the base condition, and sees that n is not less than or equal to zero, so it skips that block of code, logs 3 to the console and then calls the function again with (3-1) which is 2. Because we've called the function again, we go through the same process again - check if 2 is less than or equal to zero, skip the block of code, log 2 to the console and call the function again with (2-1) which is 1 - until we get to zero and log 'Hooray', giving us the same result as the Iterative function.

Now, you might be wondering, this is needlessly complicated and just using the for loop is better. Well, you'd be right. However, this is a very basic example to show how a recursive function works. A much better use case for a recursive function would be...

Example 2:

const tree = {
  name: "John",
  children: [
    {
      name: "Jim",
      children: [],
    },
    {
      name: "Zoe",
      children: [
        { name: "Kyle", children: [] },
        { name: "Sophia", children: [] },
      ],
    },
  ],
};

In this example code above, we have a "tree" object with nested arrays and objects and the goal is to print out the names of all the children arrays. As you might have observed, solving this problem with a for loop would be a monumental task. Now, let's tackle this using a recursive function.

function printChildrenRecursive(t) {
  if (t.children.length === 0) {
    return;
  }
  t.children.forEach((child) => {
    console.log(child.name);
    printChildrenRecursive(child);
  })
printChildrenRecursive(tree);

The code above is the solution to getting the names of the children in the tree object using a recursive function. As we can see below, if we call the function printChidrenRecursive with the argument tree, the names Jim, Zoe, Kyle and Sophia get printed to the console.

We use a recursive function in the same way as in Example 1 to achieve our goals.

We set up our base condition, which in this case is exiting the recursion if t.children.length === 0, meaning we have no values in the children array. We use the forEach method to loop the first children array because it contains two items. We traverse the array containing the name Jim on the first iteration of the loop, log the name to the console, and then execute the function once more with Jim as the argument. Because we've called the function again, we are returned to the top, to the base condition, as a result of our second call to the function. We exit the recursion by finding that the object with the name Jim has a children array whose length is 0 because it is empty.

We traverse the array with the name Zoe for the second iteration of the loop, log Zoe to the console, then run the function with Zoe as the argument. The Zoe array contains two children, so after checking the initial condition, we omit the if statement and use the forEach method to loop over the array. This loop goes through each object with the name Kyle on the first iteration. We then log the name to the console and run our function with Kyle as the argument. We go through the base condition once more. Kyle has zero children, so we break out of the recursion. In the second iteration of this loop, we go through the Sophia object, log the name to the console and call our function with Sophia as the argument. We go through the base condition, Sophia has no children, so we break out of the recursion.

When and Why to Use Recursive Functions

Recursions and loops function in quite similar manners but some problems are objectively easier to solve with one or the other. Determining which solution to use depends on the specific problem you are trying to solve. However, here are a few tips on when to use recursive functions :

  • When the problem involves a deeply nested array or object (like in the example above). With a regular loop, you might have to have several layers of the loop before getting what you need which could end up being unreadable, even for yourself.

  • When working with problems that you can break down into smaller bits and combine e.g. Finding the largest number in an array of numbers.

  • When working with data structures like stacks, linked lists and queues

Cons of Using Recursive Functions

Recursive functions are really strong and amazing, but there are a couple of drawbacks to employing this approach to problem-solving.

  • Depending on the question you're attempting to solve, employing a recursive function can be trickier than using conventional functions. Therefore, make an effort to correctly identify the best solution for each use case.

  • Because they must be called repeatedly and utilize a lot of memory, recursive functions are typically slower than non-recursive functions.

  • They can be hard to read and maintain especially as the complexity of the function increases.

Conclusion

In conclusion, Recursion is a strong programming method that uses a function calling itself to address issues by partitioning them into smaller, more manageable portions. Although it may seem complicated at first, realizing its basic components—the function declaration, the base condition, and the recursive call—can substantially ease the application process.

Recursion becomes particularly useful when dealing with problems that have complex nesting, like deeply nested arrays or objects. It's also beneficial for problems that can be divided into smaller sub-problems that follow a similar pattern. Recursion is well-suited for working with certain data structures, such as stacks, linked lists, and queues, where the recursive approach can lead to elegant solutions.

However, it's crucial to take into account any potential drawbacks of recursion. Recursive functions can occasionally be more difficult to understand than their iterative equivalents, especially as the problem's complexity rises. Due to the overhead of establishing new function calls and maintaining the call stack, they may also execute more slowly. An iterative method might be used in situations when performance is essential.

Recursion is a useful tool in a programmer's toolbox that provides elegant answers to specific kinds of issues. Understanding its advantages and disadvantages will help you choose when to use recursive functions efficiently and when to use alternative tactics, even though it may not be the ideal option in all circumstances. Recursion will get easier to use as you acquire experience, and you'll learn to recognize its best uses.

Thanks for reading. Good luck!

0
Subscribe to my newsletter

Read articles from Bimbo Adesoye directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Bimbo Adesoye
Bimbo Adesoye

A Front-end Web developer driven by creating captivating and dynamic user experiences. I'm also a technical writer skilled at simplifying intricate subjects into easily understandable fragments, accessible to everyone.