Skip to main content

Welcome to a Grasp lesson

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

Lesson 15

Fibonacci: Recursion & Exponential Complexity

Recommended resources

In our previous lesson, we established the conceptual foundation of Dynamic Programming, identifying its two key properties: optimal substructure and overlapping subproblems. We used the Fibonacci sequence as a prime example of a problem with overlapping subproblems, where a simple recursive approach would lead to massive inefficiency.

Today, we will dive into that inefficiency head-on. Our goal is to implement a recursive solution for Fibonacci numbers and identify its exponential complexity. By understanding exactly why this naive approach fails, you'll gain a much deeper appreciation for the DP optimizations we'll introduce next. This lesson bridges the theory from last time with the practical problem we are about to solve.

The Recursive Approach: A Direct Translation

The Fibonacci sequence is defined by the recurrence relation:

with the base cases typically defined as and .

Given your comfort with C++, you can see that this mathematical definition translates almost directly into a recursive function. A function to calculate will call itself to calculate and and then sum their results. The recursion stops when it hits the base cases.

Let's examine a straightforward implementation and an initial analysis of its performance.

Recursion and Memoization in C++ | A Practical Guide

This guide provides a clean C++ implementation of the recursive Fibonacci function and begins to explore its performance implications.

First, briefly review the introductory section Recursive Functions for a quick refresher on the structure of a recursive function (base case and recursive call). Then, focus on the Use Case: Fibonacci Numbers section and the C++ implementation that follows. Pay close attention to how the base cases (n == 1 and n == 2) and the recursive step (return (Fibonacci(n - 1) + Fibonacci(n - 2))) directly mirror the mathematical definition.

The code you just reviewed is a perfect example of translating a recurrence relation into a program. It's clean, readable, and correct. However, as we hinted in the last lesson, its performance is a critical issue.


Visualizing the Redundancy: The Recursion Tree

The inefficiency stems from the "overlapping subproblems" property. A naive recursive implementation re-computes the same Fibonacci numbers over and over again. The best way to see this is by drawing the recursion tree for the function calls.

The C++ code for a recursive Fibonacci function and the corresponding recursion tree for an input `f(n)`. Notice how `f(n-3)` is calculated in two separate branches, and `f(n-4)` is calculated in three. This redundancy is the source of the exponential complexity.

As the diagram illustrates, computing f(n) requires f(n-1) and f(n-2). The call to f(n-1) then generates its own sub-problems, including f(n-2) and f(n-3). But wait—we already needed to compute f(n-2) for our original f(n) call! The function is blindly re-doing work. As n grows, this redundant work explodes.

To see this process animated, the following video provides an excellent step-by-step visualization for fib(4).

Big O Notation Series #7: Time Complexity Visualization of Recursive Fibonacci

This video from Kantan Coding traces the execution of the recursive function and builds the call tree visually, making the branching and redundant subproblems very clear.

Watch from the beginning up to the full tree construction for fib(4). This will help you trace the program flow from the code to the tree structure.


Quantifying the Inefficiency: Time Complexity Analysis

Now that we have the intuition, let's formally analyze the time complexity. Since you appreciate theoretical rigor, we'll approach this from a couple of angles.

1. Analysis via Recurrence Relation

Let be the time taken to compute Fibonacci(n). We can model this with a recurrence relation that mirrors the code's structure:

Here, and are the costs of the two recursive calls, and represents the constant-time work done within the function (two comparisons and one addition). For our base cases, and are constant, or .

This recurrence is notoriously difficult to solve exactly, but we can find a solid upper bound. Since , we can say:

So, we have . If we unroll this inequality:

Since is a constant, this simplifies to . The time complexity is exponential.

The following video provides a clear walkthrough of this exact derivation.

Time Complexity analysis of recursion - Fibonacci Sequence

This mycodeschool video formally sets up the recurrence relation for the Fibonacci function and uses the approximation we just discussed to prove its O(2^n) time complexity.

Watch from the derivation to see the full argument, which concludes that this is an exponential time algorithm.

2. The Tangible Impact of Exponential Growth

What does mean in practice? Let's return to the studyplan.dev article, which provides some shocking numbers by simply adding a counter to the function.

Recursion and Memoization in C++ | A Practical Guide

This section quantifies the number of function calls, making the abstract idea of exponential growth concrete.

Read the section Performance and Branching Recursion. The key takeaway is how quickly the number of calls grows: 109 calls for Fibonacci(10), but over 200 million for Fibonacci(40).

A calculation for n=40 taking hundreds of millions of operations would time out instantly in a competitive programming contest, where you typically have a budget of around operations per second. This demonstrates that the naive recursive solution is not viable.

A Tighter Bound (For the Curious)

For a problem with a recurrence like , the number of operations is, in fact, proportional to the -th Fibonacci number itself. The closed-form solution for Fibonacci numbers involves the golden ratio, .

Therefore, a tighter bound on the complexity is . While we still write this as for a simpler and valid upper bound, it's useful to know that the base of the exponent is smaller than 2. This is because the recursion tree is not a "full" binary tree; one branch is always shorter than the other (n-2 vs. n-1), as explained in the Kantan Coding video. If you're interested in the math behind the golden ratio connection, the Stack Overflow thread below provides several excellent derivations.

Computational complexity of Fibonacci Sequence - Stack Overflow

This Stack Overflow post offers multiple perspectives on the complexity analysis.

Focus on the top two answers. The first answer establishes the recurrence relation and mentions the tight bound. The second answer shows the quick mathematical derivation for the golden ratio base.


Conclusion

In this lesson, we took a concrete problem that exhibits overlapping subproblems and implemented the most direct solution. We've now seen firsthand, through visualization and formal analysis, how this leads to disastrous exponential time complexity.

Key Takeaways:

  • A direct recursive implementation of the Fibonacci sequence is a classic example of translating a mathematical recurrence into code.
  • The recursion tree for this function clearly visualizes the massive number of redundant computations, which is the hallmark of overlapping subproblems.
  • The time complexity can be described by the recurrence .
  • By finding an upper bound, we proved that the time complexity is , which is prohibitively slow for non-trivial inputs.

You now understand the "disease" of overlapping subproblems in detail. In our next lesson, we will introduce the "cure": memoization. We will modify our simple recursive code to cache the results of subproblems, drastically improving its performance from exponential to linear time.