How JavaScript works: Recursion in JavaScript, What It Is, and How it is used.
This is post # 59 of the series, dedicated to exploring JavaScript and its building components. In the process of identifying and describing the core elements, we also share some rules of thumb we use when building SessionStack, a JavaScript tool for developers to identify, visualize, and reproduce web app bugs through pixel-perfect session replay.
Introduction
Have you been faced with the challenge of solving a task that seems as if the solution of the initial task leads to a new similar task? Although this new task may seem smaller, it may require the same technique as used in the initial task resolution in solving it.
Sometimes this can go on and on generating smaller similar tasks till infinity except terminated by some commands. Notice that your quest to dismantle this task makes your codebase enormous, bulky, and uneasy to read. How then can you go about this? The answer is recursion.
In this piece, we shall be looking at what recursion is, why it is employed in software development, how recursion works in Javascript and when not to use recursion. Also, we will consider some examples of recursion in JavaScript’s use cases and then look at how recursion differs from the popular Loop functions.
What is Recursion
Recursion is a programming pattern or concept embedded in many programming languages, and JavaScript is not left out. It is a feature used in creating a function that keeps calling itself but with a smaller input every consecutive time until the code’s desired result from the start is achieved.
This tends to keep a very clean slate of codes while providing solutions to multiple tasks of similar structure. A function that operates on the concept of recursion is called a recursive function.
Below, is the syntax of a recursive function:
In the example above, we assumed the recursive function to be recursion()
. This is the simplest format any recursive function would take. Also, the second recursion()
which is outside the function block just calls the entire function for execution.
Why use Recursion
One major factor that makes recursion stand out is that it gets complex tasks done in a few lines of code as opposed to when recursion is not employed. And this makes the codebase look simple and elegant thereby saving time and allowing the speedy accomplishment of jobs. While also eliminating chunky and bulky codebase.
For instance, we can write a recursive function to calculate factorial numbers as seen in the example below:
Although this example can be replicated with loops, it can get challenging when it involves large data. This is because the stack is used to store variables in recursions and not in loops.
How JavaScript Recursion works
Recursion in Javascript works exactly as it does in other languages. To have a great understanding of how recursion works, let’s take a look at the makeup of the syntax of the recursive function above. There are three basic parts of that syntax that anyone writing a recursive function must provide. They are as follows:
- The Function Declaration
- The Base Case
- The Recursive Call command.
The Function Declaration
This is the part that declared the intended recursive function. It is done the same way the normal declaration of a new function is done in JavaScript. The declared function can take in arguments.
The Base Case
This is the foundation of every recursive function. It is the basic unit of the initial problem to be solved when the problem is broken down into smaller tasks.
It can also be viewed as the command that has the potential of terminating the recursion process if the set condition is true. Some base cases can be enveloped in an if….else statement
.
An example is thus below:
Once the stated base condition is met, the command with the return statement is executed.
The Recursion Call command.
This command or argument is responsible for the trigger of the recursive calls. Also, this is the command that tackles the main essence of the problem you are trying to solve. It is any generic JavaScript code, which depends on what the developer aims to achieve.
When all these are put in place, you can now call the function to execute using the recursion()
function. The full recursive function now looks like this;
Examples of Recursion Use Cases
Having understood how recursion works in JavaScript in the previous section, we shall now look at some use cases of recursion. This will also enable us to have a deeper understanding of the usability of recursion.
Example 1: Number Countdown
Let’s assume you intend to write a program that will give a countdown of numbers in descending order, for example, from 10 down to 1. The intended output should be like 10,9,8,7,6,5,4,3,2,1.
Writing a program that will manually decrease the value at every step and time will look bulky, but recursion tends to achieve it with fewer lines of code.
Let us take a look at this code below:
Now let us dissect the above code with respect to the parts of a recursive function syntax.
This code above declares the function descendNum
which takes in the argument StartNum
that depicts the number from which the countdown starts. This line of code also prints out the starting number.
let NextNum = StartNum — 1;
This line of code is the recursive call command. It declares a variable NextNum
to be the subtraction of 1 from the starting number thereby creating the desired decrease.
This code block happens to be the base case. The decrease in value of the starting number can only pass if the NextNum > 0
condition is met.
Example 2: String Reversal
Suppose you wish to rewrite a string from the last character to the first, recursion can be used to do it seamlessly. This string reversal can be handy in password encryption and the like.
Take a look at the code below:
The output becomes; kcatSnoisseS
. Having a look at what happens in the code block as touching recursive function makeup. The function StringRev(text)
declares the recursive function and takes in the text
argument.
Then the base case is formed. It introduces the length of the string attribute as the basis for the string reversal. If the text length is equal to 1, it returns as it is else the recursive call takes effect as seen in the code below:
Next is the recursive call command which defines the string reversal task and then the function call command that triggers the function to achieve the required output as seen in the code below:
Example 3: Finding Exponential of Numbers
Suppose you wish to write a program that gives the exponential of a given number, this can be done effortlessly with recursion. See the code below:
Here, the recursive function is declared by the function exponential(num, power)
which receives the num
and power
arguments. It is as follows;
Then the base case is developed. The condition for this recursive function is that if the power is equal to 1, the program prints out the number expected to be raised to power otherwise it executes the recursive call command. The base case is as follows:
After this comes the recursive call which performs the actual exponential task. It is as follows:
The num * exponential (num, power — 1)
does the real maths. The reason for the power — 1
argument is that in simple maths the num
is assumed to be equal to num raised to the power of 1
.
Therefore the recursive call as stated in the example becomes [31 * 3(4–1)]
.
Recursion Vs Iterators
One might wonder that most of these things can be achieved using iterators, then why use Recursion?
The answer is this:
- It has been observed that recursion is easier and simple to write than iterators.
- It is also less bulky than loops thereby making it more elegant.
- With recursion, what matters most is the condition that terminates the executive and not the number of times it has to run. But this is not sure for iterators.
Let us look at the difference between the two by looking into another example of a task performed using the two methods.
We will be writing a program that finds the sum of numbers within a given range.
Using Loop
Using Recursion
The output still remains the same.
When not to use Recursion
While recursion is a great pattern for writing cleaner codes that can be easily debugged by developers, it poses some issues. For instance, recursion causes performance issues in applications. This is because of the slower algorithm.
For example, when you run programs recursively for some time, the system tends to freeze for some time or slow down when parsing data. Therefore, it’s not a good practice to use recursion in small programs as it is overkill. Instead, it is better to utilize iterators in smaller programs that don’t involve parsing large data.
Conclusion
JavaScript recursion is all about writing a cleaner and less bulky codebase yet proffering solutions to multi-threaded tasks. In javaScript, your recursive function is ready to be executed by declaring the function with the proper base case and recursive step commands.
While recursion is a recommended pattern for writing clean code because it helps us solve complex problems in a few lines of code, there is a potential cost of using recursion — it can lead to performance issues.
So if you choose to use recursion, you should closely monitor how it impacts the performance of your code and make your products provide a better experience for your users.
Even if you feel like the proper decisions have been made, it’s always necessary to verify that this is indeed true and your users have a great experience with your product.
For us at SessionStack, optimizing every bit of our code has been crucial.
The reason is that our library gets integrated into web apps and collects data from user sessions, such as user events, DOM changes, network data, exceptions, debug messages, and so on. Capturing this data without causing any performance impact has been a challenge that we’ve successfully solved.
This data is then processed and allows you to replay user journeys as videos in order to optimize product workflows, reproduce bugs, or see where users are stuck.
There is a free trial if you’d like to give SessionStack a try.
If you missed the previous chapters of the series, you can find them here:
- An overview of the engine, the runtime, and the call stack
- Inside Google’s V8 engine + 5 tips on how to write optimized code
- Memory management + how to handle 4 common memory leaks
- The event loop and the rise of Async programming + 5 ways to better coding with async/await
- Deep dive into WebSockets and HTTP/2 with SSE + how to pick the right path
- A comparison with WebAssembly + why in certain cases it’s better to use it over JavaScript
- The building blocks of Web Workers + 5 cases when you should use them
- Service Workers, their life-cycle, and use case
- The mechanics of Web Push Notifications
- Tracking changes in the DOM using MutationObserver
- The rendering engine and tips to optimize its performance
- Inside the Networking Layer + How to Optimize Its Performance and Security
- Under the hood of CSS and JS animations + how to optimize their performance
- Parsing, Abstract Syntax Trees (ASTs) + 5 tips on how to minimize parse time
- The internals of classes and inheritance + transpiling in Babel and TypeScript
- Storage engines + how to choose the proper storage API
- The internals of Shadow DOM + how to build self-contained components
- WebRTC and the mechanics of peer to peer connectivity
- Under the hood of custom elements + Best practices on building reusable components
- Exceptions + best practices for synchronous and asynchronous code
- 5 types of XSS attacks + tips on preventing them
- CSRF attacks + 7 mitigation strategies
- Iterators + tips on gaining advanced control over generators
- Cryptography + how to deal with man-in-the-middle (MITM) attacks
- Functional style and how it compares to other approaches
- Three types of polymorphism
- Regular expressions (RegExp)
- Introduction to Deno
- Creational, Structural, and Behavioural design patterns + 4 best practices
- Modularity and reusability with MVC
- Cross-browser testing + tips for prerelease browsers
- The “this” variable and the execution context
- High-performing code + 8 optimization tips
- Debugging overview + 4 tips for async code
- Deep dive into call, apply, and bind
- The evolution of graphics
- Dockerizing a Node.js application
- A deep dive into decorators
- Best practices for data compliance
- Proxy and Reflect
- SVG and its use cases (part 1)
- Class static blocks + 6 proposed semantics
- Introduction to Graphs and Trees
- Introduction to PM2, Strongloop, and Forever + 4 tips for Production Process Managers
- Аdvanced SVG capabilities (part 2)
- Тhe publisher-subscriber pattern
- Stacks and Queues + tips for efficient implementation
- Lists vs Blockchain + implementation practices
- The module pattern + comparing CommonJS, AMD, UMD, and ES6 Modules
- The different types of conditional statements + 3 best practices
- The different ways of declaring a function + 5 best practices
- The factory design pattern + 4 use cases
- A guide to build tools + exploring Webpack, Parcel, Rollup, ES Build, and Snowpack
- Building a child process in Node.js
- Streams and their use cases
- Understanding Maps and their use cases + 4 advantages compared to Objects
- A deep dive into Webpack