Counting the files of a folder that contains folders, walking a multi-level menu, flattening a nested structure: no simple loop is enough, because you do not know in advance how many levels exist.
Recursion answers exactly that shape of problem: solving a case by reducing it to a smaller version of itself.
Definition
A function is recursive when it calls itself. It rests on two inseparable pieces: a base case, which returns a value without calling again, and a recursive case, which calls itself on a reduced version of the problem.
function factorial(n) {
if (n <= 1) return 1; // base case
return n * factorial(n - 1); // recursive case
}
console.log(factorial(5)); // 120Without the base case, the function never stops and the Call stack overflows within milliseconds. It is the first thing to write, even before the recursive call.
Where it is genuinely better
Factorial is there to explain, not to convince: a loop would do just as well. Recursion wins when the structure itself is nested.
const menu = {
name: "Root",
children: [
{ name: "Courses", children: [{ name: "JavaScript", children: [] }] },
{ name: "Blog", children: [] },
],
};
function count(node) {
return 1 + node.children.reduce((n, child) => n + count(child), 0);
}
console.log(count(menu)); // 4Writing the same thing with a loop would mean maintaining your own list of nodes left to visit. The recursive version fits on one line because it lets the stack do that work.
Recursion or loop?
- Nested structure of unknown depth: recursion, no hesitation.
- Linear walk over a list: a loop or a Array.reduce(), more direct and with no depth limit.
- Very large volumes: the loop, because every recursive call consumes a stack entry.
A recursive function that recomputes the same values over and over, like the Fibonacci sequence, becomes usable as soon as you add a cache. That is the role of Memoization.
Frequently asked questions
At what depth does it break?
The order of magnitude sits around ten thousand nested calls, with no guarantee: the limit depends on the engine and the available memory. A file tree or a menu will never get close. A list of a hundred thousand items walked recursively will, and a loop becomes necessary.
Does JavaScript optimize tail recursion?
The specification provides for it, but no mainstream engine implements it outside Safari. Writing a function in tail position, where the recursive call is the very last operation, therefore offers no protection against overflow. Do not count on it.
How do I debug a recursion that spirals?
Print the arguments on every entry into the function: if the value does not shrink from one call to the next, the base case will never be reached. That is almost always the explanation, and the fix sits either in the stopping condition or in the value passed to the next call.