Welcome to the first lesson in our module on Core Data Structures and Recursion. In the previous module, you mastered Java's functional programming capabilities, learning how to build powerful data processing pipelines with Lambdas and Streams. We concluded with flatMap(), an operation for handling nested collections common in backend systems.
We now shift gears from high-level data processing to a fundamental concept in computer science that underpins many advanced algorithms: recursion. Your goal of tackling daily Data Structures and Algorithms (DSA) problems starts here. Many data structures, especially trees and graphs, are almost impossible to work with efficiently without a solid grasp of recursion.
In this lesson, you will learn to solve simple problems using recursion to understand how the program's call stack works. We will use the classic examples of calculating a factorial and reversing a string to build a strong mental model for this powerful problem-solving technique.
1. What is Recursion?
At its heart, recursion is the process of a method calling itself to solve a progressively smaller version of the same problem. Instead of using a loop to iterate towards a solution, a recursive method breaks the problem down into a smaller piece it can solve now and a sub-problem it delegates to another call to itself.
For this to work without running forever, every recursive method must have two essential components:
- A base case: The simplest version of the problem, for which the answer is known and can be returned directly without making another recursive call. This is the stopping condition.
- A recursive case: The part of the method that breaks the problem down and calls itself with a simpler or smaller input, moving it one step closer to the base case.
To build your intuition, let's start with a video from freeCodeCamp.org that uses an excellent analogy of people waiting in a line to explain this idea of breaking down a problem and having a stopping condition.
Recursion in Programming - Full Course
Watch this segment to get a non-technical introduction to the core idea behind recursion.
Watch from the beginning until the end of the "line of people" analogy. Pay attention to the two key questions: "What's the least amount of work I can do?" (the recursive step) and "When would the process complete?" (the base case).
2. How Recursion Works: The Call Stack
The "magic" of recursion is made possible by a fundamental mechanism in program execution: the call stack. Every time a method is called in your Java program, the Java Virtual Machine (JVM) pushes a stack frame onto the call stack. This frame contains the method's parameters, its local variables, and the address to return to when it finishes.
When a method calls another method, a new frame is pushed on top. When a method returns, its frame is popped off, and execution resumes in the frame below it.
In recursion, the method keeps calling itself, so it continuously pushes new frames for itself onto the stack. This continues until a base case is reached. At that point, the chain of calls stops, and the methods start returning one by one, popping their frames off the stack. This process of returning is often called the "unwinding" of the stack.
The freeCodeCamp.org video provides another great analogy to visualize this.
Recursion in Programming - Full Course
This part of the video explains the call stack, first with a real-world analogy and then with a programming example.
First, watch the analogy about interruptions at work. This illustrates the last-in, first-out (LIFO) nature of the tasks, just like stack frames. Next, see how this applies to a chain of method calls in a program. Finally, see what happens when a recursive function lacks a base case, leading to a StackOverflowError. This is a critical concept to understand.
For a more formal, text-based explanation, the following article provides a concise and clear breakdown of the call stack.
Recursion in Java - Complete Guide With Examples and Practice Problems | Kode$word
This article will solidify your understanding of the call stack's role in recursion.
Please read the sections titled The Two Parts and How Recursion Works. This will reinforce the concepts of base/recursive cases and the mechanics of stack frames.
3. Example 1: Calculating a Factorial
The factorial function is the quintessential example of recursion. The factorial of a non-negative integer , denoted as , is the product of all positive integers up to . For example, .
Notice the self-referential pattern: , and more generally, . This structure is perfect for recursion.
- Recursive Case:
factorial(n)isn * factorial(n - 1). - Base Case: We need a point to stop. By definition, and . So, if , we can just return 1.
Here is the implementation in Java:
public class Factorial {
public static long factorial(int n) {
// Base Case: The stopping condition
if (n <= 1) {
return 1;
}
// Recursive Case: The function calls itself with a smaller problem
else {
return n * factorial(n - 1);
}
}
public static void main(String[] args) {
int number = 5;
System.out.println("Factorial of " + number + " is " + factorial(number)); // Output: 120
}
}
Let's trace factorial(5):
factorial(5)callsfactorial(4)and waits.factorial(4)callsfactorial(3)and waits.factorial(3)callsfactorial(2)and waits.factorial(2)callsfactorial(1)and waits.factorial(1)hits the base case and returns1.factorial(2)receives1, calculates2 * 1, and returns2.factorial(3)receives2, calculates3 * 2, and returns6.factorial(4)receives6, calculates4 * 6, and returns24.factorial(5)receives24, calculates5 * 24, and returns120.
The following images perfectly illustrate this process. The first shows the flow of calls and returns, while the second shows the state of the call stack at each step.


4. Example 2: Reversing a String
String reversal is another problem that can be solved elegantly with recursion. Let's think about how to break it down.
The reverse of "hello" is "olleh". We can express this as the reverse of "ello" ("olle") followed by "h".
So, reverse("hello") = reverse("ello") + "h".
- Recursive Case:
reverse(someString)isreverse(all_but_the_first_character) + first_character. - Base Case: If the string is empty or has only one character, its reverse is itself.
The following video demonstrates this logic and provides an excellent animation of the call stack during the process.
Recursion in Programming - Full Course
This segment walks through the logic and execution of reversing a string recursively.
Watch the section on string reversal. The visual trace of the call stack is particularly helpful for understanding how the characters are reassembled in reverse order as the stack unwinds.
Here's the corresponding Java code, which you can see matches the logic from the video.
public class StringReverser {
public static String reverse(String str) {
// Base Case
if (str == null || str.length() <= 1) {
return str;
}
// Recursive Case
return reverse(str.substring(1)) + str.charAt(0);
}
public static void main(String[] args) {
String original = "hello";
System.out.println("Reverse of '" + original + "' is '" + reverse(original) + "'"); // Output: 'olleh'
}
}
Notice how the work (+ str.charAt(0)) happens after the recursive call. This means the concatenation happens during the "unwinding" phase of the stack, which is what produces the reversed order.
5. A Framework for Recursive Thinking
When you encounter a new problem, it can be tricky to see the recursive solution. Here is a step-by-step framework to guide your thinking. Adopting this mindset is key to mastering recursion.
Recursion in Java - Complete Guide With Examples and Practice Problems | Kode$word
This part of the guide provides a practical framework for solving recursive problems and highlights common pitfalls.
Read the sections Step by Step and Common Mistakes. The idea of taking a "Leap of Faith" and trusting your recursive call to work on a smaller problem is the most important takeaway.
6. Your Turn: A Practical Exercise
Now it's time to apply what you've learned. Your task is to write a recursive method to find the sum of all the digits in a non-negative integer.
For example, sumDigits(123) should return 1 + 2 + 3 = 6.
Recursive thinking:
- How can you break down
sumDigits(123)? It could be3 + sumDigits(12). - The Java operators
/ 10(integer division) and% 10(modulo) will be very helpful here. - What is the base case? What's the simplest input you can have?
Use the following skeleton code and complete the sumDigits method.
public class DigitSum {
public static int sumDigits(int n) {
// Your implementation here
// 1. What is the base case?
// 2. What is the recursive step?
// Hint: Use n % 10 to get the last digit.
// Hint: Use n / 10 to get the rest of the number.
return 0; // Placeholder
}
public static void main(String[] args) {
int number = 123;
System.out.println("Sum of digits for " + number + " is: " + sumDigits(number)); // Expected: 6
int number2 = 9876;
System.out.println("Sum of digits for " + number2 + " is: " + sumDigits(number2)); // Expected: 30
int number3 = 5;
System.out.println("Sum of digits for " + number3 + " is: " + sumDigits(number3)); // Expected: 5
}
}
Click to see a possible solution
public class DigitSum {
public static int sumDigits(int n) {
// Base case: If the number is a single digit, return itself.
if (n < 10) {
return n;
}
// Recursive step: The last digit + the sum of the rest of the digits.
return (n % 10) + sumDigits(n / 10);
}
public static void main(String[] args) {
int number = 123;
System.out.println("Sum of digits for " + number + " is: " + sumDigits(number)); // Expected: 6
int number2 = 9876;
System.out.println("Sum of digits for " + number2 + " is: " + sumDigits(number2)); // Expected: 30
int number3 = 5;
System.out.println("Sum of digits for " + number3 + " is: " + sumDigits(number3)); // Expected: 5
}
}
Conclusion
In this lesson, you took a significant first step into the world of recursive problem-solving. You've established a foundational understanding that will be critical for the more complex data structures and algorithms ahead.
Key Takeaways:
- Recursion is a method of solving problems where a function calls itself with a smaller version of the input.
- Every recursive function must have a base case to stop the recursion and a recursive case to move towards the base case.
- The call stack is the mechanism that manages recursive calls. Each call adds a frame to the stack, and frames are popped off as they return.
- If a base case is missing or never reached, the call stack will run out of memory, causing a
java.lang.StackOverflowError. - You can solve recursive problems by identifying the smallest possible input (base case) and figuring out how to express a larger problem in terms of a slightly smaller one (recursive case).
In our next lesson, we will use these foundational skills to implement our first data structure from scratch: a singly linked list. You will see how recursion provides a natural way to perform operations like traversal and manipulation on linked data.