Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Visualizing Recursive Call Trees

Welcome to the second lesson on recursion. In our last session, we built a solid foundation by breaking down recursive functions into three core components: the explicit state, the base case, and the progress step. This provided a structured way to think about and write recursive code.

Today, we're going to pull back the curtain and see what's actually happening when a recursive function executes. Our goal is to demystify the "magic" of recursion by learning how to trace its flow of calls and returns. You'll learn to build a call tree, a powerful visual tool for understanding, debugging, and ultimately analyzing any recursive algorithm. This skill will be indispensable as we move on to more complex recursive patterns.

How Recursion Works: The Call Stack

As a developer with years of experience, you're likely familiar with the concept of the function call stack. Every time a function is called, the program creates an "execution context" (or stack frame) containing that function's local variables and parameters, and pushes it onto the call stack. When a function returns, its frame is popped off the stack.

Recursive functions use this very same mechanism. The key difference is that multiple frames for the same function can exist on the stack simultaneously, each with different parameter values.

Let's get a more formal understanding of this process. The following reading from Launch School's "Advanced DSA" book provides an excellent explanation, first with non-recursive functions and then applying the same logic to the factorial function we saw in the last lesson.

Exploring the Call Stack in Recursive Algorithms

This reading explains the call stack from first principles and then applies it directly to a recursive function. It provides the low-level "machine" view of what's happening during recursion.

Please read the first three main sections of the article: Start with the introduction to the call stack and its Last-In-First-Out (LIFO) nature. Continue with the non-recursive example (greet and returnHello). This establishes a baseline for how nested calls work. Finally, read the core section, The Call Stack and Recursive Function Calls. This traces factorial(5) step-by-step, showing how stack frames are pushed for each recursive call and then popped as they return values. Pay close attention to the diagrams showing the stack growing and shrinking.

The key takeaway is this: the call stack grows with each recursive call until a base case is reached. At that point, the stack begins to "unwind" as each function call receives a return value from the call it made, completes its own computation, and returns its result to the caller above it in the stack.

From Call Stack to Call Tree

While the call stack shows us the precise, moment-to-moment state of execution, it can be difficult to see the overall structure of the computation. A more intuitive and powerful visualization is the recursion tree, or call tree.

In a call tree:

  • Each node represents a specific function call with its arguments (e.g., factorial(4)).
  • An edge from a parent node to a child node means the parent function made a direct recursive call to the child function.

For a function like factorial(n), which only makes one recursive call to factorial(n-1), the call tree is just a simple, linear chain.

factorial(4) -> factorial(3) -> factorial(2) -> factorial(1)

But what happens when a function calls itself more than once?

Branching Out: Tree Recursion

A function that contains more than one recursive call in its body is said to use tree recursion. This is where the call tree becomes a true branching structure, vividly illustrating the computational process. The classic example is computing Fibonacci numbers.

The Fibonacci sequence is defined as:

  • for

Let's watch a short video that introduces this concept clearly.

Tree Recursion

This video by John DeNero from UC Berkeley defines tree recursion and walks through the creation of the call tree for the Fibonacci function.

Please watch the first three minutes. This portion defines tree recursion and introduces the recursive Fibonacci function. This section visually constructs the call tree for fib(5), showing how the problem branches into subproblems.

As the video shows, the fib function in TypeScript would look like this:

function fib(n: number): number {
  // Base cases
  if (n <= 1) {
    return n;
  }
  // Recursive step with two calls
  return fib(n - 1) + fib(n - 2); 
}

Let's trace fib(4). The initial call fib(4) needs the results of fib(3) and fib(2). The fib(3) call, in turn, needs fib(2) and fib(1), and so on. This unfolds into the following tree structure.

This diagram shows the complete call tree for `fib(4)`. The calls branch out until they reach the base cases (`fib(1)` or `fib(0)`), which return a value directly.

Notice the yellow box highlighting "Overlapping Subproblems." The entire sub-tree for fib(2) is computed twice! This is a critical observation that a call tree makes immediately obvious. It's a major source of inefficiency in this naive recursive solution, something we will learn to fix in a later module on Dynamic Programming.

The Order of Execution: A Depth-First Journey

A common point of confusion is the order in which the nodes in the tree are executed. JavaScript, like most languages, evaluates function arguments from left to right. In fib(n-1) + fib(n-2), the entire recursive call to fib(n-1) must complete and return its value before the call to fib(n-2) even begins.

This means the execution path follows a depth-first traversal of the call tree. It goes as deep as it can down the left-most branches first, resolves them, and then backtracks to work on the branches to the right.

This image perfectly illustrates the relationship between the call tree and the call stack at a specific moment in time.

On the left is the call tree for `f(4)`. On the right is a snapshot of the call stack at step 3. At this moment, the program is executing `f(2)`, which was called by `f(3)`, which was called by `f(4)`. The execution is deep inside the leftmost branch of the tree.

To see this in action, let's return to the video on tree recursion, which animates the precise order of computation.

Tree Recursion

This segment animates the exact sequence of calls and returns for fib(5), making the depth-first traversal order very clear.

Please watch from this section. Notice how the execution goes all the way down to compute the first part of the sum (fib(3)) before it even starts on the second part (fib(4)).

Let's Practice: Tracing a New Function

Now it's time to put this skill into practice. Let's trace the execution of the following function for an initial call of mystery(4).

function mystery(n: number): number {
  if (n < 0) {
    return 2;
  }
  return mystery(n - 1) + mystery(n - 3);
}

We will build the call tree and find the final return value. A key technique when tracing by hand is to avoid re-computing branches. Once you've figured out the result for a call like mystery(1), you can just write down that result every subsequent time you see it.

recursive trace java

This video provides a fantastic, step-by-step walkthrough of how to trace a tree-recursive function. Although it's in Java, the logic is identical. Watching this will give you a template for how to approach this kind of problem on your own.

Watch the entire video from start to finish. The presenter methodically traces mystery(4) using nested boxes, which is another way to visualize the call tree. Pay close attention to how he reuses the results of calls he has already computed (e.g., once he knows mystery(0) = 4, he doesn't expand it again).

Let's summarize the trace for mystery(4) using a tree diagram.

  1. mystery(4) -> calls mystery(3) + mystery(1)
  2. mystery(3) -> calls mystery(2) + mystery(0)
  3. mystery(2) -> calls mystery(1) + mystery(-1). Base case mystery(-1) returns 2. Now we need mystery(1).
  4. mystery(1) -> calls mystery(0) + mystery(-2). Base case mystery(-2) returns 2. Now we need mystery(0).
  5. mystery(0) -> calls mystery(-1) + mystery(-3). Both are base cases, returning 2. So, mystery(0) returns 2 + 2 = 4.
  6. Back to mystery(1): It can now compute 4 + 2 = 6. So, mystery(1) returns 6.
  7. Back to mystery(2): It can now compute 6 + 2 = 8. So, mystery(2) returns 8.
  8. Back to mystery(3): It needs mystery(0). We already know mystery(0) = 4! So, mystery(3) computes 8 + 4 = 12.
  9. Back to mystery(4): It needs mystery(1). We already know mystery(1) = 6! So, mystery(4) computes 12 + 6 = 18.

The final answer is 18. By drawing the tree and reusing results, we can systematically solve the problem without getting lost.

Conclusion

In this lesson, we demystified recursive execution by connecting it to the familiar concept of the call stack and introducing the call tree as a powerful visualization tool.

Here are the main takeaways:

  • Recursive calls are managed by the function call stack, just like any other function call.
  • A call tree is a diagram that maps out all the recursive calls made during an execution. Each node is a call, and its children are the calls it makes.
  • Tree recursion occurs when a function makes more than one recursive call, leading to a branching call tree.
  • The execution of a call tree happens in a depth-first order.
  • Call trees are invaluable for understanding a recursive algorithm's behavior, identifying inefficiencies like overlapping subproblems, and debugging.

In our next lesson, we will use the call tree to perform our first real algorithmic analysis. We will learn how to estimate the total number of calls, the maximum recursion depth (which corresponds to the maximum memory used by the call stack), and the associated risk of a stack overflow error in JavaScript.

Can't find a good explanation? Sign up and we'll make it for you

Sign up