The primary cause of the maximum call stack size exceeded error is a recursive function that never terminates. This tutorial takes a deeper dive into the origin of the error and how you can solve it.
You will first understand JavaScript functions. We will then write a recursive function and understand how the call stack handles it. Lastly, we will generate the range error and teach you how to debug and solve it.
Let's get started.
Understand JavaScript functions
A function is a code block that performs a specific task. Creating a function is referred to as declaring a function. There are two main types of functions in JavaScript: regular and arrow functions.
// regular function
function regularFunction() {
// do something
}
// arrow function
const arrowFunction = () => {
// do something
}
The difference between a regular function and an arrow function is that a regular function is hoisted while an arrow function is not. Simply put, you can call a regular function before declaring it, while that does not happen with an arrow function.
Calling a function makes it start working.
// calling a regular function before declaring it
regularFunction()
function regularFunction() {
// do something
}
const arrowFunction = () => {
// do something
}
// calling an arrow function after declaring it
arrowFunction()
A regular or arrow function can be anonymous if it does not have a name. That mainly happens when the function is used as a callback function. A callback function is used as an argument to another.
const button = document.querySelector('button')
button.addEventListener('click', function () {
// do something
})
button.addEventListener('click', () => {
// do something
})
The regular function () {}
and arrow () => {}
functions get called when the click event occurs. Another way to use the callback functions is to declare them elsewhere before referencing them in their callers.
const button = document.querySelector('button')
button.addEventListener('click', regularFunction)
function regularFunction() {
// do something
}
const arrowFunction = () => {
// do something
}
button.addEventListener('click', arrowFunction)
We can program a function to run automatically without calling it. The function is then called an immediately invoked function expression (IIFE).
(function anIIFE() {
// do something
})()
Lastly, a function can call itself. Such a function is said to be recursive.
const factorialOf = (n) => {
if (n <= 1) return 1
return n * factorialOf(n-1)
}
console.log(factorialOf(5))
The function factorialOf(n-1)
keeps calling itself with a smaller portion of the input n
until the input becomes zero. How does that happen? Find out below.
What happens when you call a function? Understand JavaScript call stack
Your JavaScript code interacts with the CPU through a browser or runtime environment like Node.js. The browser or Node.js wraps a JavaScript engine.
The JavaScript engine is the interface that determines the interpretation or Just-In-Time compilation of your code. It creates a stack to handle function calls.
The stack receives, runs, and releases a function depending on the calling or urgency order. The engine determines how many times recursion occurs in the stack. The count is called recursion depth.
Each JavaScript engine has its maximum recursion depth. Side effects show when your function exceeds the maximum recursion depth. For example, your browser may freeze or throw a range error with the message, "Maximum call stack size exceeded."
Let's see code examples that could make the call stack to throw the error: Maximum call stack size exceeded.
An example of maximum call stack size exceeded error
A recursive function that never terminates is the main origin of the range error: Maximum call stack size exceeded. The function often terminates when it hits a base case: it cannot reduce the input further.
In our Fibonacci implementation, we told the function to stop calling itself when the input's value reduces to 1 or below.
if (n <= 1) return 1
The stopping condition if (n <= 1) return 1
is the base case. What happens when we (forget or) remove the base case?
Let's find out.
Input
const factorialOf = (n) => {
return n * factorialOf(n-1)
}
console.log(factorialOf(5))
Output
/home/user/GoLinuxCloud/index.js:2
return n * factorialOf(n-1)
^
RangeError: Maximum call stack size exceeded
at factorialOf (/home/user/GoLinuxCloud/index.js:2:5)
at factorialOf (/home/user/GoLinuxCloud/index.js:2:16)
at factorialOf (/home/user/GoLinuxCloud/index.js:2:16)
at factorialOf (/home/user/GoLinuxCloud/index.js:2:16)
at factorialOf (/home/user/GoLinuxCloud/index.js:2:16)
at factorialOf (/home/user/GoLinuxCloud/index.js:2:16)
at factorialOf (/home/user/GoLinuxCloud/index.js:2:16)
at factorialOf (/home/user/GoLinuxCloud/index.js:2:16)
at factorialOf (/home/user/GoLinuxCloud/index.js:2:16)
at factorialOf (/home/user/GoLinuxCloud/index.js:2:16)
Node.js v18.12.1
We get a range error RangeError: Maximum call stack size exceeded
.
We can print a cleaner error message or handle the error using try-catch blocks.
input
try {
const factorialOf = (n) => {
return n * factorialOf(n-1)
}
console.log(factorialOf(5))
} catch (e) {
console.log("The recursive function FAILED with this error message:", e.message)
}
Output
The recursive function FAILED with this error message: Maximum call stack size exceeded
After catching the exception, we can debug the code using the browser's Dev Tools or the Node.js debugger.
Conclusion
The key takeaway from this tutorial is that improperly handled functions could lead to hitting the JavaScript engine's maximum recursion depth. As a result, the call stack throws the range error: RangeError: Maximum call stack size exceeded.
The most immediate solution is to look for recursive functions without bases or debug the code as this tutorial recommends. Alternatively, you can test code in try-catch blocks with unique error messages.