Hello! Welcome to the first lesson in our module on Recursion and Backtracking. Recursion is a powerful and elegant way to solve problems that can be broken down into smaller, self-similar subproblems. For many developers, it represents a different way of thinking, and mastering it is a significant step in leveling up your algorithmic skills.
In this lesson, we will focus on the fundamental building blocks of any recursive solution. Our goal is to demystify recursion by breaking it down into a clear, repeatable pattern. By the end of our 60 minutes, you will be able to define a recursive function using three core components: an explicit state, a base case, and a progress step. This will provide you with the foundational mental model needed to tackle more complex recursive problems later in the course.
What is Recursion?
At its heart, recursion is a problem-solving technique where a function calls itself. Imagine you have a set of Russian Matryoshka dolls and you want to find the smallest one. You open the largest doll to find a slightly smaller doll inside. You then repeat the exact same action—opening the doll—on this new, smaller doll. You continue this process until you open a doll and find it's solid; it contains no others. This is your stopping point.
This analogy directly maps to the structure of a recursive function.
Best Javascript Recursion Explanation on YouTube
The DevSage video "Best Javascript Recursion Explanation on YouTube" offers a great intuitive start. The instructor uses the Matryoshka doll analogy to explain the core idea.
Watch from the beginning to the explanation of recursion, the two main parts (base case and recursive call), and the doll analogy. This will help you build a solid non-technical intuition for the concept.
As the video explains, every recursive function has two essential parts:
- Base Case: The simplest version of the problem, which can be solved directly without making another recursive call. In our doll analogy, this is finding the solid doll. It's the condition that stops the recursion.
- Recursive Step: The part of the function that calls itself. Critically, it must call itself with an input that is "smaller" or "simpler" in some way, moving it closer to the base case. In our analogy, this is opening a doll to reveal the next smaller doll.
A Classic Example: The Factorial Function
Let's translate this into code. The factorial function is a classic first example because it clearly demonstrates both the base case and the recursive step. The factorial of a non-negative integer n, denoted n!, is the product of all positive integers less than or equal to n. For example, .
Notice the self-similarity: is just , and is just , and so on, until we reach . The factorial of 0, , is defined as 1.
Here is a recursive implementation in TypeScript:
function factorial(n: number): number {
// Base Case: The simplest cases, 0! and 1!, are both 1.
if (n <= 1) {
return 1;
}
// Recursive Step: n! = n * (n-1)!
else {
return n * factorial(n - 1);
}
}
console.log(factorial(4)); // Output: 24
Let's break down how factorial(4) is computed. Each function call creates a new "frame" on the call stack. The function can't finish and return a value until the recursive call it depends on has finished.

factorial(4)is called. Since4 > 1, it must wait for the result of4 * factorial(3).factorial(3)is called. Since3 > 1, it must wait for3 * factorial(2).factorial(2)is called. Since2 > 1, it must wait for2 * factorial(1).factorial(1)is called. Now, the conditionn <= 1is true. This is the base case. It doesn't make another recursive call; it simply returns1.- The value
1is returned to thefactorial(2)call, which can now complete its calculation:2 * 1 = 2. It returns2. - The value
2is returned to thefactorial(3)call, which computes3 * 2 = 6and returns6. - Finally, the value
6is returned to the originalfactorial(4)call, which computes4 * 6 = 24and returns the final answer.
The Three Pillars of a Recursive Definition
This example perfectly illustrates the three components you need to define any recursive function. Thinking in these terms provides a reliable structure for tackling recursive problems.
1. The Base Case
This is the condition that stops the recursion. Without it, the function would call itself forever, leading to a "stack overflow" error.
- Rule: Always identify the simplest possible input(s) for which the answer is known directly.
- In
factorial(n): The base case isn <= 1.
2. The Progress Step
This is the part that drives the problem toward the base case. Each recursive call must be on a subproblem that is in some sense "smaller" or "closer" to a base case.
- Rule: Ensure that the arguments to the recursive call are different from the current arguments in a way that guarantees progress.
- In
factorial(n): The progress step is callingfactorial(n - 1). Sincen-1is smaller thann, we are guaranteed to eventually reach the base casen=1. A common mistake is to forget to change the argument, like callingfactorial(n), which would lead to an infinite loop.
3. The Explicit State
The "state" of a recursive call is all the information it needs to solve its particular subproblem. This information is passed through the function's parameters.
- Rule: Define what variables are needed to characterize a single subproblem.
- In
factorial(n): The state is extremely simple. The only piece of information needed is the current numbern. So, the state is just(n). - For a more complex problem, like finding a path in a grid, the state might be
(currentRow, currentColumn). The state defines "where we are" in the problem.
Let's look at another example to solidify this.
A Framework for Recursive Thinking
Having "algo phobia" is common, especially with topics like recursion. A structured approach can transform an intimidating problem into a manageable one.
5 Simple Steps for Solving Any Recursive Problem
The video "5 Simple Steps for Solving Any Recursive Problem" by Reducible provides an excellent framework for exactly this. It encourages assuming the recursion works for smaller problems—a concept called the "recursive leap of faith."
Please watch from the beginning to the end of the first example. Focus on the five steps presented and how they are applied to the problem of summing integers.
Let's summarize that excellent five-step process:
- Simplest Input: What's the absolute easiest case? This becomes your base case.
- Play with Examples: Work through a few inputs by hand to build intuition.
- Relate Harder to Simpler: How can you solve
problem(n)if you already have the answer toproblem(n-1)? This is the core of the recursive step. - Generalize the Pattern: Write down the relationship you found in step 3 as a general formula.
- Write the Code: Combine your base case (from step 1) and your general pattern (from step 4) into a function.
Worked Example: Number-to-Base Conversion
Let's apply this framework to a new problem: converting an integer to a string in a given base (e.g., base 2 for binary, base 10 for decimal).
Problem: function stringValue(n: number, base: number): string
-
Simplest Input (Base Case): What's the easiest number to convert? Any number that is already a single digit in the target base. If
n < base, the string representation is just the number itself. For example,stringValue(7, 10)is just"7". So, our base case isif (n < base) { return String(n); }. -
Play with Examples:
stringValue(10, 2)->"1010"stringValue(829, 10)->"829"
-
Relate Harder to Simpler: Let's take
stringValue(829, 10). The last digit is9. We can get this with the modulo operator:829 % 10 = 9. The rest of the number is"82". Where does"82"come from? It's the result ofstringValue(82, 10). And82isMath.floor(829 / 10).
So, it seemsstringValue(n, base)is related tostringValue(Math.floor(n / base), base). -
Generalize the Pattern:
stringValue(n, base) = stringValue(Math.floor(n / base), base) + String(n % base)
This looks correct! The recursive call handles the leading digits, and we append the last digit. -
Write the Code:
function stringValue(n: number, base: number): string {
// 1. Base Case
if (n < base) {
return String(n);
}
// 3 & 4. Recursive Step (Progress & General Pattern)
else {
const remainingPart = stringValue(Math.floor(n / base), base);
const lastDigit = String(n % base);
return remainingPart + lastDigit;
}
}
console.log(stringValue(10, 2)); // "1010"
console.log(stringValue(255, 16)); // "1515" ... hmm, that's not right. 255 is "FF".
// Our current code only works for digits 0-9.
// This is a good example of how testing reveals edge cases!
// We will stick to base <= 10 for now to keep it simple.
console.log(stringValue(829, 10)); // "829"
Let's re-examine this implementation against our three pillars:
- Base Case:
n < base. This correctly stops the recursion. - Progress Step:
Math.floor(n / base)is always smaller thann(forn >= base, base >= 2), so we are making progress towards the base case. - Explicit State: The state is
(n, base). It tells us which number we are currently trying to convert and in what base.
Conclusion
In this lesson, we've established a solid foundation for understanding and writing recursive functions. We've seen that what can seem like magic is actually a well-defined process built on a few core principles.
Here are the key takeaways:
- Recursion is a technique where a function calls itself to solve smaller instances of the same problem.
- Every recursive function must have a base case, a stopping condition that prevents infinite loops.
- Every recursive call must represent a progress step, moving the problem closer to a base case.
- The function's parameters define the explicit state of the subproblem being solved.
- Using a structured thinking process—like the five steps we covered—can make designing recursive solutions much more manageable.
In our next lesson, we will delve deeper into how recursion works under the hood. We'll learn how to trace recursive calls and their return values using a call tree, which is an invaluable skill for visualizing, debugging, and analyzing the performance of your recursive algorithms.
Can't find a good explanation? Sign up and we'll make it for you
Sign up