Course topics

By WebNest Studio Engineering Team

Java - Core Tutorial

Java Recursion

Recursion is a technique where a method solves a problem by calling itself with a smaller or simpler version of the same problem, until it reaches a case simple enough to answer directly. It is an alternative to iteration (loops) for expressing repetitive computation, and it is especially natural for problems that are already defined in terms of themselves, such as factorials, tree traversals, and many divide-and-conquer algorithms.

Every correct recursive method needs two distinct parts: a base case that stops the recursion, and a recursive case that makes progress toward that base case. Get either one wrong, and the method either never recurses (wrong answer) or never stops (a StackOverflowError).

Why it matters

Some problems are defined in terms of smaller versions of themselves: walking a folder tree, traversing a nested structure, dividing a list in half. Recursion expresses those directly. It is also a favourite interview topic because it tests whether you can reason about the call stack — and because a missing base case fails in a very recognisable way.

Base Case vs Recursive Case

The base case is the condition under which the method returns a value directly, without calling itself again — it is what eventually stops the recursion. The recursive case is where the method calls itself, but always with an argument that is "closer" to the base case than the current call (a smaller number, a shorter list, a node one level deeper). If the recursive case doesn't reliably shrink the problem, or the base case condition is never actually reached, the recursion never terminates.

The Call Stack: Building Up and Unwinding

Each time a method calls itself, the JVM pushes a new stack frame onto the call stack, holding that call's local variables and its return address. Calls keep building up (going "deeper") until the base case is hit, at which point that innermost frame returns a value. Then the frames unwind one at a time, back down the stack, with each waiting call resuming exactly where it paused, using the value just returned to complete its own calculation, and returning its own result to the frame below it. This build-up-then-unwind pattern is why tracing recursion visually as a stack of pending calls is so useful.

  • factorial(4) calls factorial(3), which calls factorial(2), which calls factorial(1), which calls factorial(0) — the base case.
  • factorial(0) returns 1 immediately, without recursing further.
  • factorial(1) resumes, computes 1 * 1 = 1, and returns 1.
  • factorial(2) resumes, computes 2 * 1 = 2; factorial(3) computes 3 * 2 = 6; factorial(4) computes 4 * 6 = 24.

Recursion vs Iteration

Anything expressible recursively can also be expressed iteratively with a loop, and vice versa. Recursion often produces shorter, more readable code for naturally self-referential problems (tree/graph traversal, backtracking, divide-and-conquer algorithms like merge sort), because the code mirrors the problem's own definition. Iteration is usually more memory-efficient, since it doesn't grow the call stack, and can be faster because it avoids the overhead of repeated method calls. As a rule of thumb, prefer iteration for simple linear repetition, and reach for recursion when the problem structure is naturally hierarchical or self-similar.

StackOverflowError

Each recursive call consumes stack memory, and the JVM's call stack has a fixed, finite size. If a recursive method is missing its base case, has a base case that is never reached due to a logic error, or simply recurses too deeply for a very large input, the stack fills up and the JVM throws a StackOverflowError. This is an Error, not an Exception, signaling a serious problem with the program's structure rather than an expected, recoverable condition — though it can technically still be caught with a try-catch block.

Examples

Tracing factorial and Fibonacci recursion to a correct result

Java
public class RecursionDemo {

    static int factorial(int n) {
        if (n == 0) {          // base case
            return 1;
        }
        return n * factorial(n - 1); // recursive case
    }

    static int fibonacci(int n) {
        if (n <= 1) {           // base case
            return n;
        }
        return fibonacci(n - 1) + fibonacci(n - 2); // recursive case
    }

    public static void main(String[] args) {
        System.out.println(factorial(5));
        System.out.println(fibonacci(7));
    }
}
Output
120
13

A missing base case leads to a StackOverflowError

Java
public class RecursionErrorDemo {

    // Bug: no condition ever stops the recursion
    static int badRecursion(int n) {
        return badRecursion(n + 1);
    }

    public static void main(String[] args) {
        try {
            badRecursion(0);
        } catch (StackOverflowError e) {
            System.out.println("Caught: StackOverflowError due to a missing base case");
        }
    }
}
Output
Caught: StackOverflowError due to a missing base case

Common Mistakes

  • Writing a recursive case without a matching base case, or with a base case that the recursive calls never actually reach.
  • Forgetting to make forward progress — calling the method again with the exact same argument instead of a smaller one, causing infinite recursion.
  • Using naive recursion (like the fibonacci example above) for large inputs without memoization, resulting in exponential time complexity from repeated recalculation of the same subproblems.
  • Assuming recursion is always "cleaner" — for simple counting or summing loops, a plain for-loop is usually clearer and cheaper than a recursive equivalent.

Exercise

Write a recursive method that returns the sum of the digits of a non-negative number. Do not use a loop.

Expected output: 19 for 4096 (4 + 0 + 9 + 6), then 7 for 7.

Java · starter code
public class DigitSum {

    static int sumDigits(int n) {
        // TODO: base case - a single digit is its own sum
        // TODO: recursive case - last digit plus the sum of the remaining digits
        return 0;
    }

    public static void main(String[] args) {
        System.out.println(sumDigits(4096));
        System.out.println(sumDigits(7));
    }
}
Show hints
  • n % 10 is the last digit and n / 10 is the number without it.
  • The base case is when n is less than 10.
Show solution
Java · solution
public class DigitSum {

    static int sumDigits(int n) {
        if (n < 10) {
            return n;
        }
        return n % 10 + sumDigits(n / 10);
    }

    public static void main(String[] args) {
        System.out.println(sumDigits(4096)); // 19
        System.out.println(sumDigits(7));    // 7
    }
}

Quick quiz

1. What stops a recursive method from calling itself forever?

  • A. The return type
  • B. A base case that returns without recursing
  • C. The static keyword
  • D. The garbage collector
Show answer

B. The base case handles the smallest input directly. Every recursive call must move towards it.

2. A method computes n! recursively as n * factorial(n - 1), with factorial(1) returning 1. What does factorial(4) return?

  • A. 10
  • B. 16
  • C. 24
  • D. 4
Show answer

C. 4 * 3 * 2 * 1 = 24. The multiplications happen as the calls return, from the base case back up.

3. What happens when a recursive method has no base case?

  • A. It returns 0
  • B. It loops forever without error
  • C. It does not compile
  • D. It throws StackOverflowError
Show answer

D. Each call adds a frame to the call stack. With nothing to stop it, the stack fills up and the JVM throws StackOverflowError.

Interview questions

What are the trade-offs between recursion and iteration?

Show answer

Recursion often matches the shape of the problem and gives shorter, clearer code for trees and divide-and-conquer algorithms. Its cost is one stack frame per call, so deep recursion uses more memory and can overflow the stack.

Iteration uses constant stack space and is usually a little faster, but may need an explicit stack or extra variables to track state that recursion keeps for you.

Does Java optimise tail recursion?

Show answer

No. The Java compiler and the standard JVM do not perform tail-call elimination, so a tail-recursive method still uses one stack frame per call and can still throw StackOverflowError on deep input. If depth is a concern in Java, rewrite the method as a loop.

Key Points to Remember

  • Every recursive method needs a base case (stops recursion) and a recursive case (makes progress toward the base case).
  • Each call adds a frame to the call stack; frames unwind in reverse order once the base case is reached, combining results on the way back down.
  • Recursion trades stack memory and call overhead for code that can mirror a naturally self-referential problem more directly than a loop.
  • A missing or unreachable base case causes unbounded recursion and eventually a StackOverflowError.

Practice in the playground

Write and run your own code for this topic, or start from the exercise or an example. Runs plain Java 17. Frameworks such as Spring, and databases, are not available here. Your code for each topic is kept in this browser.

Run the examples

Change an input, predict the result, then compare it with the output. Explain why the result changes.

WebNest Studio

Written by

WebNest Studio Engineering Team

Practical lessons from developers working with Java, Python, React, databases and production systems.

Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering