Lecture 15: The Stack is Calling!

Back in Week 3, while we were talking about Memory Management, we took a moment to learn the basics of one of the most important concepts in practical programming: the Call Stack.

Today, we’ll spend some more time with the Call Stack, as well as discover how a solid understanding of it helps us keep our heads on straight when designing and debugging recursive algorithms.

The Call Stack, Revisited

The Call Stack is a data structure that stores information about the active functions of a computer program.

What does the Call Stack store?

Stack Frames

A stack frame is what gets pushed onto the Call Stack every time a function is called - every function call has a stack frame, which holds a few pieces of key information: For the below points, consider some function a(), which called some function b()

  • The parameters sent to a function being called from whatever function was calling it
    • a() calls b(param x, param y)
  • The variables that are currently in scope for the currently running function
    • any variables created inside of b()
  • A “return address” pointing to where it was called within the function that called it
    • a return address to a(), the function that called b()

Stack Frames provide us a handful of advantages!

Once a function is done running (once it reaches the end of it’s code or returns), it is popped off of the Call Stack, and any data returned from it is sent to the function that called it (via the return address).

So the Call Stack helps us answer a few questions:

  1. (Q) What is the currently executing function? (A) The function represented by the Stack Frame at the top of the Call Stack.
  2. (Q) What variables are currently in scope? (A) The local variables and parameters stored in the Stack Frame at the top of the Call Stack.
  3. (Q) When a function returns a value, where does that return value go? (A) To the Stack Frame at the current return address (the function that called it, at the line of code where it was called)
  4. (Q) When a function finishes executing, what executes next? (A) That function’s Stack Frame is popped, and control is returned to the Stack Frame that is now at the top of the Stack, at the line of code within that function that the return address points to.

Let’s look at a programmatic case where careful tracking of our Call Stack is useful:

Recursion

A recursive algorithm is an algorithm that calls itself at some point in its execution.

thank you freeCodeCamp for the graphic

Designing a recursive function is all about finding the right structure - we begin by asking ourselves two questions:

  1. What is the Base Case of my function?
  2. What is the Recursive Case of my function?

I’ll answer these backwards:

The Recursive Case of your function is the condition under which you want your function to call itself again (this time usually with a new argument). However, we need to know what condition to stop and not call ourselves again, which leads to…

The Base Case of your function, the condition under which, based on the argument or some other condition, we can be certain about value we want to return - this terminates our recursion and begins the process of returning values back up to the original function call.

Let’s put that into practice:

Example

Design a recursive function static long factorial(int n) which returns the factorial of a given number n.

Factorials are a great intro to recursion! Factorials are a mathematic concept you’ve likely already come across! But as a reminder, we define a factorial on some number as:

As a note, we know that

Let’s ask ourselves our two questions for factorial(n):

  1. What is the Base Case of my function?
    1. The base case of is when , in which case
  2. What is the Recursive Case of my function?
    1. Any case when , in which case the answer is

What does that look like in code?

public static long factorial(int n) {
    if (n <= 1) {
        return 1;
    }
    return n * factorial(n - 1);
}

Okay, now that we’ve done that, lets return to the Call Stack -

Practice in the Call Stack

We wrote a recursive function to calculate the factorial of some number - let’s draw out the stack diagram on a running of the function!

Factorial

Example

Draw out the Call Stack diagram for each step of the below code:

public static long factorial(int n) {
    if (n <= 1) {
        return 1;
    }
    return n * factorial(n - 1);
}
 
...
 
System.out.println(factorial(4));

Remember the other day, when we found the intuition for Binary Search on a sorted list?

Steps of Binary Search

  1. Start by examining the element at the middle index.
  2. If it is your query, you’re done, otherwise: > - If your query is less than the examined element, then repeat from step 1 on the left half of the list. > - If your query is greater than the examined element, then repeat from step 1 on the right half of the list.

I’ve written a recursive method to perform Binary Search on a sorted list:

public static int binarySearch(int query, int leftIndex, int rightIndex, int[] corpus) {
    if (leftIndex > rightIndex) {
        return -1;
    }
 
    int mid = (leftIndex + rightIndex) / 2;
 
    if (query < corpus[mid]) {
        return binarySearch(query, leftIndex, mid - 1, corpus);
    } else if (query > corpus[mid]) {
        return binarySearch(query, mid + 1, rightIndex, corpus);
    } else { // corpus[mid] == query
        return mid;
    }
}
 
...
 
int[] arr = {1, 5, 8, 32, 45, 99, 100, 108};
System.out.println(binarySearch(45, 0, arr.length - 1, arr));

Example

Draw out the Call Stack diagram for each step of the above code.

Fibonacci

More math! Sorry…

The Fibonacci sequence is a sequence of integers, starting with 0 and 1, where any given number in the sequence is equivalent to the prior two numbers added together. More formally:

You might be noticing where this gets recursive…

Let’s ask ourselves our two questions for fibonacci(n):

  1. What is the Base Case of my function?
    1. The base case is is when and , in which case
  2. What is the Recursive Case of my function?
    1. Any case when , in which case the answer is
public static long fibonacci(int n) {
    if (n <= 1) {
        return n;
    }
    return fibonacci(n - 1) + fibonacci(n - 2);
}

This one has a bit of an interesting case I’d like to look at when it comes to our Call Stack diagrams… fibonacci()’s recursive case calls itself twice in one line. What does the progression of the stack look like when it runs?

Example

Draw out the Call Stack diagram for each step of fibonacci(7)

Towers of Hanoi

Towers of Hanoi is a game you might be familiar with - you’re given three rods, upon one of which sits a stack of discs of different diameters: smallest on the top, largest on the bottom. Your goal is to move all of the discs from Rod 1 to Rod 3, following the given constraints:

  1. Only one disk may be moved at a time.
  2. Each move consists of taking the upper disk from one of the stacks and placing it on top of another stack or on an empty rod.
  3. No disk may be placed on top of a disk that is smaller than it.

It’s a bit unintuitive at first, but Towers of Hanoi is a game with a surprisingly simple recursively algorithmic solution.

For any Towers of Hanoi for number of discs from Rod A to Rod C, simply solve the game for discs from Rod A to Rod B,, move the largest disc onto Rod C, then solve again for Rod B onto Rod C.

public static void towerOfHanoi(int n, char fromRod, char toRod, char auxRod) {
    if (n == 1) {
        // Oh boy. I get to explain string interpolation. Yay.
        System.out.printf("Move disk %d from rod %c to rod %c%n", n, fromRod, toRod);
        return;
    }
    towerOfHanoi(n - 1, fromRod, auxRod, toRod);
    System.out.printf("Move disk %d from rod %c to rod %c%n", n, fromRod, toRod);
    towerOfHanoi(n - 1, auxRod, toRod, fromRod);
}

Let’s walk through the Call Stack diagram of Towers of Hanoi, and this algorithm might make a little more sense.

Example

Draw out the Call Stack diagram for each step of tower_of_hanoi(3, 'A', 'C', 'B')