Fibonacci with Dynamic Programming

Almost every dynamic programming course starts with Fibonacci. There is a good reason for that. The plain version looks simple. But it hides a problem that makes your code painfully slow. Once you see the problem, the dynamic programming fix makes a lot of sense. So let us start with the slow code. We will watch it struggle. Then we will fix it step by step.

🧮 What Is the Fibonacci Series?

The Fibonacci series is a list of numbers. Each number is the sum of the two before it. The first two numbers are 0 and 1. After that, you just keep adding.

So the series goes like this:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...

See the pattern? 0 plus 1 is 1. Then 1 plus 1 is 2. Then 1 plus 2 is 3. And it keeps going.

In math we write it as a recurrence. That just means a rule that defines a value using earlier values:

fib(0) = 0
fib(1) = 1
fib(n) = fib(n - 1) + fib(n - 2)

Our job is simple. Given a number n, find fib(n).

🐌 The Slow Way: Plain Recursion

The recurrence above almost writes the code for itself. If n is 0 or 1, return n. Otherwise return fib(n - 1) + fib(n - 2). That is it.

Here is that idea in Python:

def fib(n):
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)

This is correct. It gives the right answer. But it is shockingly slow for larger n. Why? Let us look at what it actually computes.

Say we ask for fib(5). To get that, it needs fib(4) and fib(3). To get fib(4), it needs fib(3) and fib(2). Notice fib(3) already showed up twice. And it only gets worse deeper down.

Here is the call tree:

fib(5)

fib(4)

fib(3)

fib(3)

fib(2)

fib(2)

fib(1)

fib(2)

fib(1)

Look at fib(3). We compute it once on the left and again on the right. The same work, done twice. For bigger n the same small problems get solved again and again, thousands of times. We call these repeated problems overlapping subproblems. That means the big problem keeps breaking into the same smaller problems.

Caution

Plain recursive Fibonacci runs in about O(2^n) time. For n = 50 that is tens of billions of calls. Your program will look frozen. This wasted, repeated work is exactly what dynamic programming removes.

💡 The DP Idea: Remember What You Already Solved

Dynamic programming is one simple idea. If you are going to solve the same small problem again and again, solve it once and save the answer. Next time you need it, just read the saved answer instead of computing it again.

There are two common ways to do this.

  • Memoization (top-down): keep the recursion, but store each answer in a table the first time you compute it. Before computing fib(k), check the table. If it is already there, return it.
  • Tabulation (bottom-up): drop the recursion. Start from the smallest values and fill an array from fib(0) upward until you reach fib(n).

Let us see the bottom-up version, because it is the cleanest. We make an array dp where dp[i] holds fib(i). We set dp[0] = 0 and dp[1] = 1. Then we walk forward and fill each spot using the two spots before it.

def fib(n):
if n <= 1:
return n
dp = [0] * (n + 1)
dp[1] = 1
for i in range(2, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]

Now each value is computed exactly once. That brings the time down from O(2^n) to a clean O(n). That is a huge difference.

✂️ One More Step: Save Only the Last Two Values

The tabulation version is fast. But look closely at the loop. To compute dp[i], we only ever use dp[i - 1] and dp[i - 2]. We never look further back. So why keep the whole array in memory?

We do not need to. We can keep just two variables for the last two values, and one more to hold the new sum. This is the space-optimized version. That means we cut down the extra memory we use.

Here is how it works in points:

  • Keep prev2 for fib(i - 2) and prev1 for fib(i - 1). Start them at 0 and 1.
  • In each step, the next value is prev1 + prev2.
  • Then slide the window forward. prev2 becomes the old prev1. And prev1 becomes the new value.
  • After the loop, prev1 holds the answer.

This keeps the O(n) speed but uses only O(1) extra space. That is the best version. So let us write it in all five languages.

🛠️ Steps to Compute Fibonacci (Space-Optimized)

Before the code, here is the full plan in order.

  1. Handle the small cases first. If n is 0 or 1, just return n.
  2. Set prev2 = 0 (this is fib(0)) and prev1 = 1 (this is fib(1)).
  3. Loop i from 2 up to n.
  4. Compute current = prev1 + prev2.
  5. Slide the window. Set prev2 = prev1, then prev1 = current.
  6. When the loop ends, prev1 is fib(n). Return it.

💻 Code in 5 Languages

We compute fib(10), which should be 55. We also print the first 11 values so you can see the series build up.

fibonacci.py
# Space-optimized Fibonacci: O(n) time, O(1) extra space
def fib(n):
if n <= 1:
return n # small cases
prev2 = 0 # fib(0)
prev1 = 1 # fib(1)
for i in range(2, n + 1):
current = prev1 + prev2 # next value
prev2 = prev1 # slide the window
prev1 = current
return prev1 # fib(n)
print("First 11 Fibonacci numbers:", " ".join(str(fib(i)) for i in range(11)))
print("fib(10) =", fib(10))

The output of the above code will be:

First 11 Fibonacci numbers: 0 1 1 2 3 5 8 13 21 34 55
fib(10) = 55

Tip

The space-optimized version runs in O(n) time and O(1) extra space. We make one pass from 2 to n. And we only ever hold two old values plus the new one. There is no faster or lighter way to compute Fibonacci this simply.

📊 Comparing the Three Approaches

Here is how the three versions stack up. Same problem, very different cost.

Approach Time Extra Space Notes
Plain recursion O(2^n) O(n) Recomputes the same values again and again. Too slow.
Memoization (top-down) O(n) O(n) Recursion plus a cache. Each value computed once.
Tabulation (bottom-up) O(n) O(n) Fills an array from the bottom up. No recursion.
Space-optimized O(n) O(1) Keeps only the last two values. The best version.

Note

For very large n, the numbers grow so big they overflow normal integer types in C, C++, and Java. Python handles big integers on its own. In interviews, the question usually asks for the answer modulo a number like 10^9 + 7 to keep values small. So watch for that.

⚠️ Common Mistakes to Avoid

A few small slips trip people up here. Keep an eye out for these.

  • Forgetting the small cases. If you do not return early for n = 0 and n = 1, your loop math breaks.
  • Sliding the window in the wrong order. You must save prev1 into prev2 and then put the new value into prev1. Swap that order and you lose a value.
  • Starting the loop at the wrong place. The first two values are already known. So the loop begins at i = 2, not at 0 or 1.
  • Sticking with plain recursion in an interview. It works for tiny n. But it shows you missed the overlapping subproblems. Always reach for the DP version.

🧩 What You’ve Learned

  • ✅ The Fibonacci series adds the two previous numbers, with fib(0) = 0 and fib(1) = 1.
  • ✅ Plain recursion is correct but runs in O(2^n) time because it solves the same subproblems again and again.
  • ✅ Dynamic programming fixes this by solving each subproblem once and saving the answer.
  • ✅ Memoization is top-down, tabulation is bottom-up, and both run in O(n) time.
  • ✅ The space-optimized version keeps only the last two values, giving O(n) time and O(1) extra space.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    Why is plain recursive Fibonacci so slow for large n?

    Why: The same values like fib(3) get computed many times, leading to about O(2^n) total work.

  2. 2

    What is the difference between memoization and tabulation?

    Why: Memoization keeps recursion and caches results, while tabulation drops recursion and fills a table from the smallest values up.

  3. 3

    In the space-optimized version, why do we only need two variables?

    Why: fib(i) uses only fib(i - 1) and fib(i - 2), so keeping just those two values is enough.

  4. 4

    What are the time and space complexities of the space-optimized Fibonacci?

    Why: We make a single pass from 2 to n and store only a constant number of values, so it is O(n) time and O(1) extra space.

🚀 What’s Next?

Share & Connect