Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 3

Understanding Space Complexity

Recommended resources

Hello! Welcome back to your course on Data Structures and Algorithms.

In our last lesson, we focused on the time complexity of recursive algorithms, using tools like the Master Theorem. We saw how function calls unfold over time. Today, we'll examine the other side of the coin: the memory these calls consume.

This lesson addresses the learning outcome: Analyze space complexity, distinguishing between input size, auxiliary space, and call stack usage. Understanding this distinction is critical in interviews, where you're often expected to go beyond a correct solution and discuss its resource efficiency. You'll learn to identify how much memory an algorithm uses and, crucially, where that memory is being allocated.

1. The Components of Space Complexity

When we talk about "space complexity," we're really talking about a combination of different types of memory usage. A clear understanding of these components is essential, as interviewers might ask you to analyze one specifically.

At a high level, the total space an algorithm uses can be broken down into two main parts:

Space Complexity Breakdown Flowchart
This flowchart illustrates the two primary components of total space complexity: the space required to hold the input and the extra space used during computation.
  1. Input Space: The memory required to store the input data itself. For example, if your function takes an array of n integers, the input space is O(n).
  2. Auxiliary Space: The extra or temporary memory that an algorithm uses during its execution. This includes variables, data structures created inside the function, and, as we'll see, the memory used by function calls.

The following video provides a concise introduction to this distinction.

Time and Space Complexity - Strivers A2Z DSA Course

The video 'Time and Space Complexity' from the take U forward channel clearly defines the difference between input space and auxiliary space.

Watch from 25:54 to 29:26. Pay close attention to how the narrator defines total space as input space + auxiliary space and uses a simple example to illustrate the difference.

The Interviewer's Perspective

In a technical interview, when you're asked for the "space complexity," the interviewer is most often interested in the auxiliary space complexity. Why? Because the input space is a given—you can't change it. The interesting part is how much extra memory your algorithm requires to solve the problem.

A crucial piece of advice for interviews is to always clarify. A simple question like, "Should I include the space for the input in my analysis, or are you interested in the auxiliary space?" shows precision and a deep understanding of the topic.

The article Space Complexity: How Algorithms Use Memory from DataCamp will be our main reference for this lesson. Let's start by reading the section that formalizes these components.

Space Complexity: How Algorithms Use Memory

This article provides a comprehensive breakdown of the different components that contribute to an algorithm's memory footprint.

Read the section titled 'Components of Space Complexity'. Focus on the definitions of 'Input space requirements' and 'Auxiliary space and temporary memory', especially the note about what's common in coding interviews. Estimated time: 5 minutes.

Test your understanding!

Consider the following Python function that finds the maximum element in an array. What is its input space complexity and its auxiliary space complexity?

def find_max(numbers):
    if not numbers:
        return None
    
    max_val = numbers[0]  # A new variable
    for i in range(1, len(numbers)):
        if numbers[i] > max_val:
            max_val = numbers[i]
            
    return max_val

# Let n = len(numbers)
Show answer
  • Input Space Complexity: The function takes an array numbers of size n. The space required to store this input is proportional to n. So, the input space is O(n).
  • Auxiliary Space Complexity: The algorithm uses a few extra variables (max_val, i) to do its work. The amount of memory for these variables does not change, no matter how large the input array is. Therefore, the auxiliary space is constant, or O(1).

2. The Call Stack and Recursive Space

In our last lesson, we saw that a recursive function calls itself repeatedly. The system needs to keep track of where it is in the execution of each call. It does this using the call stack.

Each time a function is called, a new stack frame is pushed onto the call stack. This frame stores information like the function's parameters, local variables, and the return address (where to go back to after the function finishes). This stack of frames consumes memory.

Space Complexity of Recursive Algorithms (The Call Stack)
Each function call, like `fun(5, 7)`, adds a new frame to the call stack, consuming memory. When the function returns, its frame is popped off the stack.

For iterative algorithms, the call stack depth is usually constant (O(1)). But for recursive algorithms, the stack can grow significantly, and this growth must be included in our auxiliary space analysis.

The key insight is that the space complexity is not determined by the total number of recursive calls, but by the maximum depth of the call stack at any point during execution.

To understand this crucial point, we'll watch a video that explores the space complexity of recursive functions in detail.

Finding the Space Complexity of Recursive Algorithms

The video 'Finding the Space Complexity of Recursive Algorithms' from Neso Academy provides an excellent explanation of why recursion depth, not total calls, determines space usage.

Watch from 10:43 to 17:41. Focus on the concepts of 'depth of function calls' (also called 'depth of recursion') and how it directly translates to the maximum number of activation records on the stack. This will give you the final formula for calculating recursive space complexity.

So, the space complexity of a recursive algorithm is determined by:

Since the space per stack frame is usually constant, this simplifies to:

Let's apply this to a few examples we've seen before.

  • Recursive Factorial: factorial(n) = n * factorial(n-1)

    • The recursion goes n -> n-1 -> ... -> 1. The maximum depth of the call stack is n.
    • Auxiliary Space: O(n).
  • Recursive Binary Search: binary_search(arr, target, low, high)

    • The recursion halves the search space at each step: n -> n/2 -> n/4 -> ... -> 1. The number of calls in the stack at any time is the number of times you can divide n by 2, which is logâ‚‚(n).
    • Auxiliary Space: O(log n).

Now, return to the DataCamp article and read the section that discusses the call stack's role.

Space Complexity: How Algorithms Use Memory

Let's reinforce the concept of call stack space with our main reading resource.

Revisit the article and read the subsection 'The call stack and recursion space' within the 'Components of Space Complexity' section. Note the examples given for linear and binary recursion. Estimated time: 5 minutes.


3. A Systematic Approach to Analysis

Now we can combine these ideas into a step-by-step method for analyzing the space complexity of any algorithm, perfect for a whiteboard interview setting.

  1. Clarify Assumptions: Start by asking if the analysis should include input space or focus solely on auxiliary space. The default is usually auxiliary space.
  2. Identify Non-Recursive Space:
    • Count fixed-size variables (pointers, counters, etc.). These contribute O(1).
    • Measure any auxiliary data structures created (e.g., a new array, a hash map). Their space complexity will depend on how their size relates to the input n.
  3. Identify Recursive Space:
    • If the algorithm is recursive, determine the maximum depth of the call stack. This will be O(n) for linear recursion, O(log n) for binary-splitting recursion, etc.
  4. Combine and Simplify:
    • Add the space from all components.
    • The final space complexity is determined by the dominant term. For example, if an algorithm has O(n) recursion depth and also creates an O(1) auxiliary variable, the total auxiliary space is O(n).
Test your understanding!

Analyze the auxiliary space complexity of the recursive Fibonacci function. Then, analyze the space complexity of the dynamic programming version.

# Version 1: Recursive
def fib_recursive(n):
    if n <= 1:
        return n
    return fib_recursive(n - 1) + fib_recursive(n - 2)

# Version 2: Dynamic Programming (Bottom-up)
def fib_dp(n):
    if n <= 1:
        return n
    
    dp_array = [0] * (n + 1) # Auxiliary data structure
    dp_array[1] = 1
    
    for i in range(2, n + 1):
        dp_array[i] = dp_array[i - 1] + dp_array[i - 2]
        
    return dp_array[n]
Show answer
  • fib_recursive: The function is recursive. The longest path in the call tree before hitting a base case is n -> n-1 -> ... -> 1. The maximum depth of the call stack is therefore n. The auxiliary space complexity is O(n). (Note: its time complexity is exponential, O(2^n), but the space is linear because only one path of the recursion tree exists on the stack at a time).

  • fib_dp: This function is iterative, so its call stack usage is O(1). However, it creates an auxiliary data structure, dp_array, of size n+1. The space required for this array grows linearly with n. Therefore, the auxiliary space complexity is O(n).

This is a great example where two very different algorithms can have the same space complexity but for completely different reasons (call stack vs. an explicit data structure).


Conclusion

You have now completed the fundamentals of complexity analysis! By understanding both time and space, you can have a much richer conversation about algorithm performance in an interview.

Key Takeaways:

  • Total space complexity is comprised of input space and auxiliary space.
  • In interviews, the focus is typically on auxiliary space. It's always best to clarify this assumption.
  • Auxiliary space includes memory for temporary variables, any new data structures you create, and the call stack.
  • The space used by a recursive algorithm is proportional to its maximum recursion depth, not the total number of calls made.

In our next lesson, we will explore one of the most classic themes in algorithm design: the time-space tradeoff. We'll see how you can often make an algorithm faster by using more memory, or more memory-efficient by sacrificing some speed. This concept, which we briefly saw in the Fibonacci example, is a cornerstone of advanced algorithm optimization.