JavaScript Recursion Patterns
Introduction
Recursion is a powerful programming technique where a function calls itself to solve a problem by breaking it down into smaller, similar subproblems. In functional programming, recursion serves as an alternative to imperative loops, often resulting in more elegant and readable solutions.
This guide explores common recursion patterns in JavaScript, providing you with the knowledge to recognize and implement these patterns in your own code. By the end, you'll understand how recursion can elegantly solve complex problems in a functional style.
Understanding Basic Recursion
Before diving into specific patterns, let's quickly review the basic structure of recursive functions:
function recursiveFunction(input) {
// Base case: condition to stop recursion
if (someCondition) {
return baseValue;
}
// Recursive case: call the function with a modified input
return recursiveFunction(modifiedInput);
}
Every recursive function needs:
- A base case to prevent infinite recursion
- A recursive case that moves toward the base case
Common Recursion Patterns
1. Linear Recursion
Linear recursion is the simplest pattern where a function calls itself once per recursive step.
Example: Factorial Calculation
function factorial(n) {
// Base case
if (n <= 1) return 1;
// Recursive case
return n * factorial(n - 1);
}
console.log(factorial(5)); // Output: 120
How it works:
factorial(5)
returns5 * factorial(4)
factorial(4)
returns4 * factorial(3)
factorial(3)
returns3 * factorial(2)
factorial(2)
returns2 * factorial(1)
factorial(1)
returns1
(base case)- Then we work backwards:
2 * 1 = 2
,3 * 2 = 6
,4 * 6 = 24
,5 * 24 = 120
2. Tail Recursion
Tail recursion occurs when the recursive call is the last operation in the function. This pattern is important because many functional languages optimize tail recursion to prevent stack overflow errors.
Example: Factorial with Tail Recursion
function factorialTail(n, accumulator = 1) {
// Base case
if (n <= 1) return accumulator;
// Tail recursive case
return factorialTail(n - 1, n * accumulator);
}
console.log(factorialTail(5)); // Output: 120
How it works:
factorialTail(5, 1)
callsfactorialTail(4, 5)
factorialTail(4, 5)
callsfactorialTail(3, 20)
factorialTail(3, 20)
callsfactorialTail(2, 60)
factorialTail(2, 60)
callsfactorialTail(1, 120)
factorialTail(1, 120)
returns120
(base case)
Note: While JavaScript doesn't automatically optimize tail recursion, this pattern is still valuable for understanding functional programming concepts.
3. Binary Recursion
Binary recursion occurs when a function calls itself twice in the same function call.
Example: Fibonacci Sequence
function fibonacci(n) {
// Base cases
if (n <= 0) return 0;
if (n === 1) return 1;
// Recursive case with two calls
return fibonacci(n - 1) + fibonacci(n - 2);
}
console.log(fibonacci(7)); // Output: 13
How it works:
- For each number n, we calculate the sum of the two previous Fibonacci numbers
- This creates a tree of recursive calls, which can be inefficient for large values
4. Mutual Recursion
Mutual recursion involves two or more functions that call each other.
Example: Even and Odd Checker
function isEven(n) {
if (n === 0) return true;
if (n < 0) return isEven(-n);
return isOdd(n - 1);
}
function isOdd(n) {
if (n === 0) return false;
if (n < 0) return isOdd(-n);
return isEven(n - 1);
}
console.log(isEven(4)); // Output: true
console.log(isOdd(7)); // Output: true
How it works:
isEven
andisOdd
call each other with a decremented value- Each call brings us one step closer to the base case (zero)
- Very elegant solution for determining if a number is even or odd
5. Accumulator Pattern
This pattern uses an additional parameter to accumulate results during recursion.