Senior 9 min · March 05, 2026

Fibonacci Dynamic Programming - Integer Overflow at n=47

Negative Fibonacci from int overflow at n=47 in Java silently corrupts production load balancing.

N
Naren Founder & Principal Engineer

20+ years shipping performance-critical code where algorithms decide the bill. Drawn from code that ran under real load.

Follow
Production
production tested
May 23, 2026
last updated
1,554
articles · all by Naren
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Naive recursion is O(2^n) because it recomputes overlapping sub-problems endlessly
  • Memoization adds a cache to recursion — top-down, O(n) time, O(n) space
  • Tabulation builds a table from the bottom up — O(n) time, O(1) space possible
  • Performance: fib(50) with naive recursion takes hours; with DP it's microseconds
  • Production insight: Integer overflow strikes at n=47 if you use int; always use long or BigInteger
  • Biggest mistake: forgetting to initialise memo with -1 (since fib(0)=0 is a valid answer)
✦ Definition~90s read
What is Fibonacci with DP?

Fibonacci dynamic programming is a technique that transforms the naive recursive computation of Fibonacci numbers from exponential O(2^n) time to linear O(n) time by eliminating redundant calculations. The core problem it solves is that a straightforward recursive implementation recalculates the same subproblems exponentially — for example, fib(5) calls fib(3) twice and fib(2) three times.

Imagine you're asked the same maths question 50 times in a row — you'd write the answer on a sticky note after the first time and just read it instead of re-calculating.

DP fixes this by storing results of subproblems in a table (array or hash map) and reusing them, either through top-down memoization (caching recursive calls) or bottom-up tabulation (iteratively building from base cases). This is the canonical example for teaching DP because it's simple enough to grasp but illustrates the fundamental trade-off: space for time, with O(n) memory typically required.

In practice, Fibonacci DP is rarely used in production — you'd just use Binet's formula for O(1) computation or iterative O(1) space. But it's the textbook introduction to DP patterns like overlapping subproblems and optimal substructure, which apply to real-world problems like shortest paths (Dijkstra's), sequence alignment (Smith-Waterman), and resource allocation (knapsack).

The technique breaks at n=47 in 32-bit signed integers because Fibonacci numbers exceed 2^31-1 (1,836,311,903 vs. 2,147,483,647), causing integer overflow. This limitation forces you to use arbitrary-precision integers (like Python's big ints or Java's BigInteger) or switch to modular arithmetic for cryptographic applications.

When not to use DP: if you only need a single Fibonacci number, use the closed-form formula; if you need the sequence up to n, the iterative O(1) space approach is simpler and faster than memoization's recursion overhead.

Plain-English First

Imagine you're asked the same maths question 50 times in a row — you'd write the answer on a sticky note after the first time and just read it instead of re-calculating. That sticky note trick is exactly what Dynamic Programming does for Fibonacci. The naive approach recalculates fib(3) dozens of times; DP writes down each answer the moment it's found and reuses it instantly. It turns a slow, forgetful algorithm into a fast, remembering one.

Fibonacci numbers look deceptively simple — 0, 1, 1, 2, 3, 5, 8, 13… — but they hide one of the most important lessons in computer science: redundant computation is the silent killer of performance. Interviewers use Fibonacci precisely because it's a lens that reveals whether you understand why brute-force recursion fails at scale. Google, Meta, and Amazon engineers routinely ask candidates to optimise it, because the thinking you apply here transfers directly to harder DP problems like longest common subsequence, coin change, and edit distance.

The core problem is this: computing the 50th Fibonacci number with plain recursion makes over 2 billion function calls. That's not an exaggeration — the call tree branches like a fracklace, recalculating the same sub-results exponentially. Dynamic Programming (DP) fixes this by storing results the first time they're computed so they're never calculated twice. The difference between O(2^n) and O(n) is the difference between your program taking years versus milliseconds.

By the end of this article you'll be able to explain — out loud, to an interviewer — why naive recursion is slow, implement both DP approaches (memoization and tabulation) from scratch in Java, compare their trade-offs, and avoid the three mistakes that trip up most beginners. No prior DP knowledge is assumed. We'll build everything piece by piece.

What Fibonacci Dynamic Programming Actually Eliminates

Fibonacci dynamic programming replaces the naive recursive tree with a linear computation by caching overlapping subproblem results. The core mechanic is simple: store each computed Fibonacci number so that F(n) = F(n-1) + F(n-2) becomes an O(n) lookup and addition instead of an O(2^n) explosion of repeated calls. This is the textbook case of top-down memoization or bottom-up tabulation — both eliminate the exponential waste.

In practice, the bottom-up approach iterates from F(0) and F(1) upward, building an array of size n+1. This gives O(n) time and O(n) space, which can be further reduced to O(1) space by keeping only the last two values. The key property: every subproblem is solved exactly once. For n=46, the 46th Fibonacci number (1836311903) fits in a 32-bit signed integer; at n=47, it overflows to a negative value — a silent corruption that breaks any production system relying on correctness.

Use this technique whenever a problem exhibits optimal substructure and overlapping subproblems — the two hallmarks of dynamic programming. In real systems, Fibonacci is rarely the end goal, but it’s the canonical model for understanding how to transform exponential recursion into linear iteration. Master this pattern, and you can apply it to sequence alignment, pathfinding, and resource allocation where naive recursion would be infeasible.

Integer Overflow Is Not Theoretical
At n=47, Fibonacci overflows int in Java — the result wraps to negative. Use long (overflow at n=93) or BigInteger for correctness in production.
Production Insight
A financial risk engine used naive recursion to compute Fibonacci-based sequence numbers for trade IDs — at n=47, IDs became negative, causing downstream reconciliation failures.
Symptom: trade IDs suddenly negative, no exception thrown, silent data corruption in the audit log.
Rule: always validate integer range for any DP recurrence that grows exponentially — switch to BigInteger or cap n before overflow.
Key Takeaway
Memoization and tabulation both achieve O(n) time — choose tabulation for lower overhead and O(1) space.
The naive recursive Fibonacci is O(2^n) — never use it beyond n=30 in production.
Integer overflow at n=47 is a real, silent failure — always check your numeric type’s capacity.
Fibonacci DP: From Recursion to Tabulation THECODEFORGE.IO Fibonacci DP: From Recursion to Tabulation Optimizing Fibonacci with memoization and tabulation, plus overflow at n=47 Naive Recursion Exponential O(2^n) time, repeated work Memoization (Top-Down) Cache results, O(n) time, recursion overhead Tabulation (Bottom-Up) Iterative DP, O(n) time, no recursion Integer Overflow n=47 exceeds 32-bit signed int max Climbing Stairs Classic DP problem, same recurrence ⚠ Fibonacci(47) overflows 32-bit int in many languages Use 64-bit (long) or arbitrary precision for n > 46 THECODEFORGE.IO
thecodeforge.io
Fibonacci DP: From Recursion to Tabulation
Fibonacci Dynamic Programming

Why Naive Recursion Destroys Performance — The Problem We're Solving

Before we fix anything, we need to feel the pain of the broken version. The recursive Fibonacci definition is beautiful on paper: fib(n) = fib(n-1) + fib(n-2). But when you translate that directly into code, something ugly happens under the hood.

Every call to fib(n) spawns two more calls. Those each spawn two more. By the time you want fib(50), the call tree has over two billion nodes — and most of them are duplicates. fib(48) is calculated twice, fib(47) three times, fib(46) five times. The overlap grows exponentially.

Think of it like baking a birthday cake by going to the shop to buy flour every single time you need a cup of it — including mid-bake. You'd make fifty trips. The sensible thing is to buy all the flour at the start and measure from the bag. DP is that bag of flour.

The time complexity of naive recursion is O(2^n). At n=50, that's roughly 1,125,899,906,842,624 operations. Modern laptops do about 10^9 operations per second — so you'd be waiting over two weeks. This is not theoretical; run the code below and watch your terminal freeze at fib(50).

NaiveFibonacci.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class NaiveFibonacci {

    /**
     * Naive recursive Fibonacci — correct but catastrophically slow.
     * Time complexity: O(2^n)  — doubles the work for every extra step.
     * Space complexity: O(n)   — call stack depth equals n.
     */
    public static long fib(int n) {
        // Base cases: fib(0) = 0, fib(1) = 1 — the sequence's starting seeds
        if (n == 0) return 0;
        if (n == 1) return 1;

        // Every call spawns TWO more calls — this is where the explosion happens
        return fib(n - 1) + fib(n - 2);
    }

    public static void main(String[] args) {
        // These small values are fine — the tree is tiny
        System.out.println("fib(5)  = " + fib(5));
        System.out.println("fib(10) = " + fib(10));
        System.out.println("fib(20) = " + fib(20));

        // Count how many times fib(3) is recalculated inside fib(10)
        // (It's called 8 times — wasteful even at n=10)
        System.out.println("\nfib(40) = " + fib(40)); // starts to feel slow
        // DO NOT try fib(60) here — it will run for minutes
        System.out.println("\nNotice fib(40) was slow. Imagine fib(80).");
    }
}
Output
fib(5) = 5
fib(10) = 55
fib(20) = 6765
fib(40) = 102334155
Notice fib(40) was slow. Imagine fib(80).
Watch Out:
fib(50) with naive recursion takes several minutes on modern hardware. If you're live-coding in an interview and you write this version, say immediately: 'This is O(2^n) — let me optimise it with memoization.' Showing self-awareness is half the battle.
Production Insight
In production, no one writes naive recursion Fibonacci. But the same pattern appears in graph traversals and tree searches that don't memoize.
A poorly-designed recursive crawler can bring down a web scraper by spawning duplicated requests.
Rule: if you see the same sub-problem recalculated more than once, add a cache immediately.
Key Takeaway
Naive recursion is O(2^n)
Do not use it for n > 30
Always call out the performance problem when writing it in interviews.
When to Use Naive Recursion (Almost Never)
Ifn < 30 and only used once
UseNaive recursion is fine for tiny inputs in throwaway scripts.
Ifn >= 30 or called repeatedly
UseNever use naive recursion — use memoization or tabulation.
IfYou're in an interview and wrote this version
UseMention the O(2^n) problem immediately and offer to DP-optimise.

Memoization — The Top-Down DP Approach (With a Notebook Analogy)

Memoization is the first way to apply DP. The word comes from 'memo' — as in, you write a memo to yourself so you don't repeat work. The strategy is top-down: start at the big problem (fib(n)), break it down recursively as before, but this time write down every answer in a notebook (an array or HashMap) the first time you compute it. Next time the same question comes up, just read from the notebook.

Here's the analogy in full: imagine you're a student and your teacher keeps asking you random Fibonacci questions during class. The first time they ask 'what's fib(10)?', you work it out and write '55' next to 'fib(10)' in your notebook. The next time they ask fib(10) — even mid-calculation of fib(12) — you just glance at your notebook. Zero thinking required.

The code change is minimal but the impact is massive. We add a memo array initialised to -1. Before computing, we check: 'have I seen this before?' If yes, return the stored answer. If no, compute, store, then return.

Time complexity drops from O(2^n) to O(n). Space complexity is O(n) for the memo table plus O(n) for the call stack — so O(n) overall. Every unique sub-problem is solved exactly once.

MemoizedFibonacci.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import java.util.Arrays;

public class MemoizedFibonacci {

    /**
     * Top-Down Dynamic ProgrammingMemoization.
     * We use a memo array as our 'notebook'.
     * Time:  O(n) — each unique fib value computed exactly once.
     * Space: O(n) — memo array + recursive call stack.
     */
    public static long fib(int n, long[] memo) {
        // Base cases — the two seeds every Fibonacci sequence starts from
        if (n == 0) return 0;
        if (n == 1) return 1;

        // CHECK THE NOTEBOOK FIRST — did we already compute this?
        // memo[n] != -1 means we've been here before and stored the answer
        if (memo[n] != -1) {
            return memo[n]; // Return instantly — zero recursion needed
        }

        // First time visiting fib(n) — compute it the normal recursive way...
        long result = fib(n - 1, memo) + fib(n - 2, memo);

        // ...then WRITE IT IN THE NOTEBOOK before returning
        memo[n] = result;

        return result;
    }

    public static void main(String[] args) {
        int target = 50;

        // Create the memo array, sized (target + 1) so index n maps to fib(n)
        // Fill with -1 to mean 'not yet computed'
        long[] memo = new long[target + 1];
        Arrays.fill(memo, -1);

        System.out.println("=== Memoized Fibonacci ===");

        // These will all be instant — even fib(50) completes in microseconds
        for (int i = 0; i <= 10; i++) {
            // Reuse the same memo table — values computed earlier help later calls
            System.out.printf("fib(%2d) = %d%n", i, fib(i, memo));
        }

        System.out.println("...");
        System.out.printf("fib(50) = %d%n", fib(target, memo));
        System.out.println("\nCompleted instantly. Compare that to naive recursion.");
    }
}
Output
=== Memoized Fibonacci ===
fib( 0) = 0
fib( 1) = 1
fib( 2) = 1
fib( 3) = 2
fib( 4) = 3
fib( 5) = 5
fib( 6) = 8
fib( 7) = 13
fib( 8) = 21
fib( 9) = 34
fib(10) = 55
...
fib(50) = 12586269025
Completed instantly. Compare that to naive recursion.
Pro Tip:
Initialise your memo array with -1, not 0. fib(0) is legitimately 0, so using 0 as your 'not computed' sentinel will make your code incorrectly think fib(0) is already cached — and return wrong answers. Always pick a sentinel value that can never be a real answer.
Production Insight
Memoization uses recursion, which means stack depth equals input size. At n=10,000 on a standard JVM you'll hit StackOverflowError.
In production, if n can exceed a few thousand, prefer tabulation.
If you must keep memoization for large n, increase the thread stack size with -Xss or use a trampoline pattern.
Key Takeaway
Memoization caches already-computed results.
Time drops from O(2^n) to O(n).
Risks: stack overflow for large n and incorrect sentinel value.
Memoization or Tabulation for Large n?
Ifn <= 10,000 and recursion depth is safe
UseMemoization is acceptable.
Ifn > 10,000 or recursion depth is unknown
UseUse tabulation to avoid stack overflow.

Tabulation — The Bottom-Up DP Approach (No Recursion at All)

Memoization is top-down: start big, recurse down. Tabulation is the opposite — bottom-up: start from the smallest known answers and build upward deliberately, filling a table row by row.

Think of it like filling in a multiplication table at school. You don't start at the 12×12 corner and work backwards — you start at 1×1 and fill forward because each cell depends only on cells you've already filled.

For Fibonacci: fib(0) = 0 and fib(1) = 1 are our starting seeds. From there, every subsequent value is just the sum of the previous two entries in our table. We never recurse. No call stack. Just a simple loop.

This is generally preferred in production code for three reasons: no risk of stack overflow for large n, slightly faster in practice due to no function call overhead, and easier to reason about memory usage. The space can even be reduced to O(1) by keeping only the last two values — we'll show that optimisation too.

Time complexity: O(n). Space complexity: O(n) for the full table, or O(1) with the optimised two-variable version.

TabulatedFibonacci.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public class TabulatedFibonacci {

    /**
     * Bottom-Up Dynamic ProgrammingTabulation.
     * Build the answer from the ground up using a table.
     * Time:  O(n) — single pass through the loop.
     * Space: O(n) — the fibTable array stores all intermediate results.
     */
    public static long fibTableFull(int n) {
        if (n == 0) return 0;
        if (n == 1) return 1;

        // Create a table where index i will hold the value of fib(i)
        long[] fibTable = new long[n + 1];

        // Seed the table with the two values we know for certain
        fibTable[0] = 0; // fib(0) is defined as 0
        fibTable[1] = 1; // fib(1) is defined as 1

        // Fill every cell from index 2 upward — each depends only on earlier cells
        for (int position = 2; position <= n; position++) {
            // The core DP recurrence: sum the two cells immediately before this one
            fibTable[position] = fibTable[position - 1] + fibTable[position - 2];
        }

        // The answer we want is sitting at the end of the table
        return fibTable[n];
    }

    /**
     * Space-Optimised Tabulation — O(1) space.
     * We only ever need the PREVIOUS TWO values, so we ditch the full table.
     * This is the version you want in memory-constrained environments.
     * Time:  O(n) — same single loop.
     * Space: O(1) — just three variables, no array.
     */
    public static long fibSpaceOptimised(int n) {
        if (n == 0) return 0;
        if (n == 1) return 1;

        long previousPrevious = 0; // Represents fib(i-2)
        long previous         = 1; // Represents fib(i-1)
        long current          = 0; // Will hold fib(i) each iteration

        for (int step = 2; step <= n; step++) {
            current = previous + previousPrevious; // Compute this step's fib value
            previousPrevious = previous;           // Slide the window forward
            previous = current;                    // Slide the window forward
        }

        return current;
    }

    public static void main(String[] args) {
        System.out.println("=== Tabulated Fibonacci (Full Table) ===");
        for (int i = 0; i <= 10; i++) {
            System.out.printf("fib(%2d) = %d%n", i, fibTableFull(i));
        }
        System.out.printf("fib(50) = %d%n%n", fibTableFull(50));

        System.out.println("=== Space-Optimised Fibonacci (O(1) space) ===");
        System.out.printf("fib(50) = %d%n",  fibSpaceOptimised(50));
        System.out.printf("fib(70) = %d%n",  fibSpaceOptimised(70));
        System.out.println("\nSame answers, no array needed — just three variables.");
    }
}
Output
=== Tabulated Fibonacci (Full Table) ===
fib( 0) = 0
fib( 1) = 1
fib( 2) = 1
fib( 3) = 2
fib( 4) = 3
fib( 5) = 5
fib( 6) = 8
fib( 7) = 13
fib( 8) = 21
fib( 9) = 34
fib(10) = 55
fib(50) = 12586269025
=== Space-Optimised Fibonacci (O(1) space) ===
fib(50) = 12586269025
fib(70) = 190392490709135
Same answers, no array needed — just three variables.
Interview Gold:
When an interviewer asks you to 'optimise the space complexity', they're fishing for the two-variable sliding window trick. Show fibSpaceOptimised() and explain that since fib(n) only depends on fib(n-1) and fib(n-2), you never need more than two variables in memory. This answer consistently impresses.
Production Insight
Tabulation is the safest choice for production because it uses no recursion and no risk of stack overflow.
The O(1) space version is especially useful in embedded systems or microcontrollers where memory is scarce.
One trap: if the n passed is very large (e.g., 10^9), the O(n) time becomes a problem — but for Fibonacci in a typical app, n rarely exceeds 100.
Key Takeaway
Tabulation avoids recursion entirely.
No stack overflow.
Space can be reduced to O(1).
This is the go-to implementation for production code.
Which Tabulation Variant to Choose
IfYou need to reuse all intermediate Fibonacci values (e.g., for a sequence generator)
UseUse full table (O(n) space).
IfYou only need the nth value and memory is constrained
UseUse space-optimised (O(1) space).
Ifn > 10^6
UseConsider using BigInteger and closed-form approximation (Binet's formula) for large n.

Choosing Between Memoization and Tabulation — When Each Wins

Both approaches give O(n) time. The choice usually comes down to space constraints, stack safety, and how natural the top-down vs bottom-up thinking feels to you.

Memoization wins when: - The recursive solution is almost already written (just add a cache) - You don't need all sub-problems (lazy evaluation — you only compute what's needed) - The problem has irregular dependency patterns (e.g., some branches are pruned)

Tabulation wins when: - You need all sub-problems computed anyway (Fibonacci always does) - n is large (avoiding stack overflow) - Memory is tight (O(1) space optimisation is a big deal) - You want deterministic performance (no recursion overhead)

In an interview, either is acceptable. But being able to articulate the trade-offs — and showing you understand when recursion depth becomes a problem — marks you as a senior engineer.

A concrete heuristic: if you're writing a solution for a coding problem and you already wrote the recursion, memoize it. If you're building a production service where n could be large, use tabulation.

Production Insight
I've seen teams waste hours debugging a slow memoized solution because they forgot the cache – it's the most common DP bug.
If you're building a library that computes Fibonacci for arbitrary inputs, always use tabulation. It's simpler and harder to misconfigure.
One edge case: if you need to compute Fib for tens of thousands of different n values, memoization can be more efficient because it caches all computed values and reuses them across calls.
Key Takeaway
Memoization is easier to write from a recursive solution.
Tabulation is safer for production and large inputs.
Interviewers care more about your reasoning than which one you pick.
Memoization vs Tabulation Decision Flow
Ifn <= 10,000 and recursion is comfortable
UseMemoization — simpler code.
Ifn > 10,000 or memory is critical
UseTabulation — safer and more efficient.
IfYou only need a few specific Fibonacci values
UseMemoization — lazy evaluation avoids computing unneeded values.

Limitations of Dynamic Programming for Fibonacci — When DP Breaks and What to Do

Dynamic Programming solves Fibonacci efficiently for n up to about 10^6. Beyond that, you hit two walls: integer overflow and time complexity.

Integer overflow: In Java, long maxes out at fib(92). For n > 92, you need BigInteger. BigInteger operations are slower — but still O(n) — and memory grows with the number of digits. That's fine for n up to a few hundred thousand, but beyond that the multiplications (addition in BigInteger is O(number of digits)) cause slowdowns.

Time complexity: O(n) is linear, but at n = 10^9, you have a billion iterations. That's not feasible in real time. The loop itself is fast — about 1 ns per iteration on modern CPUs — but that still gives 1 second for n = 10^6, and 1000 seconds for n = 10^9.

Alternatives for extremely large n: Use matrix exponentiation (O(log n) time) or Binet's formula with floating-point precision. Matrix exponentiation is the standard interview follow-up for "what if n is 10^12?"

Memoization-specific limitation: Stack overflow for recursive depth > ~10,000. Tabulation avoids this.

Concurrency: Both approaches are embarrassingly parallel? Not really — each step depends on the previous two. But you can compute Fibonacci using fast doubling (recursive formula) that splits into independent subproblems. That's beyond the scope of this article but worth noting for senior engineers.

FastDoublingFibonacci.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.math.BigInteger;

public class FastDoublingFibonacci {

    /**
     * Returns fib(n) using fast doubling method, O(log n).
     * Returns a pair (fib(n), fib(n+1)).
     */
    public static BigInteger[] fib(int n) {
        if (n == 0) return new BigInteger[] { BigInteger.ZERO, BigInteger.ONE };
        BigInteger[] half = fib(n >> 1);
        BigInteger a = half[0];
        BigInteger b = half[1];
        // c = a * (b*2 - a)
        BigInteger c = a.multiply(b.shiftLeft(1).subtract(a));
        // d = a*a + b*b
        BigInteger d = a.multiply(a).add(b.multiply(b));
        if ((n & 1) == 0) {
            return new BigInteger[] { c, d };
        } else {
            return new BigInteger[] { d, c.add(d) };
        }
    }

    public static void main(String[] args) {
        int n = 1000000; // one million
        BigInteger result = fib(n)[0];
        System.out.println("fib(1,000,000) computed via fast doubling");
        System.out.println("Number of digits: " + result.toString().length());
        // Uncomment to print: System.out.println(result);
    }
}
Output
fib(1,000,000) computed via fast doubling
Number of digits: 208987
Production Insight
If your production system calculates Fibonacci for n > 92, you must use BigInteger. I've fixed a load balancer that used int Fibonacci seeds and silently produced negative weights.
For n > 10^7, O(n) DP becomes too slow. Use matrix exponentiation (O(log n)) or closed-form. In Java, implement fast doubling method via BigInteger.
One more gotcha: BigInteger addition is O(number of digits) — for fib(1,000,000) the result has ~208,987 digits, so even a simple addition takes microseconds. Scale accordingly.
Key Takeaway
O(n) DP is fast up to about 10^6.
For larger n, use matrix exponentiation or fast doubling.
Always use BigInteger for n > 92.
Stack overflow kills memoization for large n — use tabulation or fast doubling.

Basic of DP: Why Fibonacci Is the Gateway Drug, Not the Destination

Most tutorials leave you with Fibonacci and act like you've seen the whole DP universe. That's a lie. Fibonacci is the "hello world" of DP — it proves you understand the mechanism, but it doesn't teach you to spot DP problems in the wild.

Real DP is about identifying overlapping subproblems and optimal substructure in problems that don't scream "recurrence relation" at you. The unbounded knapsack, edit distance, and longest increasing subsequence all share the same DNA: they decompose into smaller versions of themselves.

Here's the litmus test: If you can draw a recursion tree where the same node appears more than once, you've got a DP candidate. Fibonacci's tree is a mess of redundant nodes. But so is a shortest-path graph with cycles. So is a sequence alignment with mismatches. The patterns repeat — you just need to learn to see them.

Start with Fibonacci to internalize memoization and tabulation. Then immediately apply that skeleton to climbing stairs, then to 0/1 knapsack. That's when the intuition hardens into instinct.

DpDetectorTemplate.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// io.thecodeforge — dsa tutorial

public class DpDetectorTemplate {

    // Is this a DP candidate? Check for overlapping subproblems.
    // Use this skeleton: recurse, cache, return.
    static int cachedFib(int n, int[] cache) {
        if (n <= 1) return n;
        if (cache[n] != -1) return cache[n];
        cache[n] = cachedFib(n - 1, cache) + cachedFib(n - 2, cache);
        return cache[n];
    }

    public static void main(String[] args) {
        int n = 10;
        int[] cache = new int[n + 1];
        java.util.Arrays.fill(cache, -1);
        System.out.println(cachedFib(n, cache)); // 55
    }
}
Output
55
Senior Shortcut:
Before writing a single line of DP, sketch the recursive brute force. If the recursion tree has repeated nodes at depth > 2, you're wasting CPU. Add a cache. Done.
Key Takeaway
DP isn't a technique; it's a pattern-recognition skill. Fibonacci teaches you the mechanism, not the strategy. Apply the template to climbing stairs next — identical structure, different problem.

Basic Problems: Climbing the Stairs — Fibonacci's Disguised Twin

You've mastered Fibonacci, and now some interviewer hits you with "Count ways to climb N stairs taking 1 or 2 steps." Do you panic? No. Because you recognize it's Fibonacci in a trenchcoat.

Let's prove it: to reach stair N, you either came from stair N-1 (1 step) or stair N-2 (2 steps). So f(N) = f(N-1) + f(N-2). That's literally the Fibonacci recurrence with f(1)=1, f(2)=2. Compute it with tabulation — O(N) time, O(1) space.

But here's where it gets interesting: the moment steps change (1, 2, 3), your recurrence changes. Then weighted stairs add cost per step — that's a shortest-path DP in disguise. The shell is the same; only the recurrence condition mutates.

Master these siblings: climbing stairs, tribonacci numbers, and Lucas numbers. They're the same concept rotated. Rotate it enough times in your head, and you'll spot DP patterns in any recursive mess an interviewer throws at you.

ClimbingStairsTabulation.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// io.thecodeforge — dsa tutorial

public class ClimbingStairsTabulation {

    static int countWays(int n) {
        if (n <= 1) return 1;
        int prev2 = 1; // f(0)
        int prev1 = 1; // f(1)
        for (int i = 2; i <= n; i++) {
            int current = prev1 + prev2;
            prev2 = prev1;
            prev1 = current;
        }
        return prev1;
    }

    public static void main(String[] args) {
        int n = 5;
        System.out.println(countWays(n)); // 8 (ways: 1+1+1+1+1, 1+1+1+2, 1+1+2+1, 1+2+1+1, 2+1+1+1, 1+2+2, 2+1+2, 2+2+1)
    }
}
Output
8
Production Trap:
Never hardcode base cases for n <= 1 as 1 when N can be 0. If n=0 returns 1, your tabulation breaks for n=2. Match base cases to your recurrence's meaning: f(0)=1 (one way to stand still), f(1)=1 (one step).
Key Takeaway
Climbing stairs is Fibonacci with adjusted base cases. Spot the recurrence relation by asking: "What's the last action I took?" That action defines the subproblem.

Space Optimization — Slash Memory From O(n) to O(1) Without Breaking a Sweat

You don't need an entire array to compute the nth Fibonacci number. That tabulation table with n+1 slots? Pure waste when you only care about the last two values. Real production code chokes on memory when n hits 10 million — you'll OOM before you blink.

The trick: track only two variables. Each iteration shifts them forward like a conveyor belt. Previous becomes second-previous, current becomes previous, next gets computed fresh. That's it. No cache, no stack frames, no garbage collector headache.

This isn't just a Fibonacci optimization — it's a pattern you'll use in rolling array problems, sliding window DP, and any recurrence that only depends on the last k states. Senior engineers spot these dependencies instantly. The rest allocate memory they don't need.

SpaceOptimizedFib.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// io.thecodeforge — dsa tutorial

public class SpaceOptimizedFib {
    public static long fib(int n) {
        if (n <= 1) return n;
        long prev2 = 0, prev1 = 1;
        for (int i = 2; i <= n; i++) {
            long current = prev1 + prev2;
            prev2 = prev1;
            prev1 = current;
        }
        return prev1;
    }

    public static void main(String[] args) {
        System.out.println(fib(10)); // 55
        System.out.println(fib(50)); // 12586269025
    }
}
Output
55
12586269025
Production Trap:
Do NOT use int for Fibonacci past n=46 — overflow will silently corrupt results. Use long (n=92 cap) or BigInteger for arbitrary precision.
Key Takeaway
If your recurrence only glances at the last k states, store k variables — not the entire history.

Fast Doubling — O(log n) Fibonacci When Linear Isn't Fast Enough

O(n) still sucks when n is 10^12. Spinning a loop a trillion times won't finish before your next sprint planning. Matrix exponentiation works, but fast doubling is cleaner: two formulas that halve the problem size at every step using only multiplication.

F(2k) = F(k) (2F(k+1) - F(k)) F(2k+1) = F(k+1)^2 + F(k)^2

These identities let you skip straight to the answer. You trade addition for multiplication, but the recursion depth drops to O(log n). This is the version used in production-grade math libraries and competitive programming when the constraints laugh at O(n).

Downside: you're now dealing with big integers even for moderate n. Java's BigInteger handles that, but be ready for heap pressure. Also, implement iteratively if the recursion depth scares you — stack overflow at log2(10^12) is ~40 deep, so you're fine.

FastDoublingFib.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// io.thecodeforge — dsa tutorial

import java.math.BigInteger;

public class FastDoublingFib {
    // Returns array [F(k), F(k+1)]
    static BigInteger[] fib(int n) {
        if (n == 0) return new BigInteger[]{BigInteger.ZERO, BigInteger.ONE};
        BigInteger[] half = fib(n / 2);
        BigInteger a = half[0], b = half[1];
        BigInteger c = a.multiply(b.multiply(BigInteger.TWO).subtract(a));
        BigInteger d = a.multiply(a).add(b.multiply(b));
        return (n % 2 == 0) ? new BigInteger[]{c, d} : new BigInteger[]{d, c.add(d)};
    }

    public static void main(String[] args) {
        System.out.println(fib(100)[0]); // 354224848179261915075
    }
}
Output
354224848179261915075
Senior Shortcut:
Fast doubling is the default for n > 10^6 in production. Keep the iterative O(n) version for small n — the constant factor on BigInteger multiplication kills perf on tiny inputs.
Key Takeaway
When linear time isn't acceptable, fast doubling gives you O(log n) Fibonacci using number theory — no matrices required.
● Production incidentPOST-MORTEMseverity: high

Fibonacci Calculator in Production — Integer Overflow Caused Silent Wrong Results

Symptom
Fibonacci values above 46 produced negative numbers. No errors thrown. The load balancer started assigning negative weights, causing uneven traffic distribution.
Assumption
The team assumed long would hold all needed Fibonacci values. Nobody tested beyond 50.
Root cause
fib(47) = 2,971,215,073 — exceeds int max (2,147,483,647). The code used int return type. Overflow is silent in Java and C# — no exception, just wrapped to negative.
Fix
Changed return type to long for n up to 92, and to BigInteger (Java) or System.Numerics.BigInteger (C#) for larger values. Added unit tests for n=47 and n=93 to catch overflow early.
Key lesson
  • Any Fibonacci implementation must specify the max n that fits the chosen data type.
  • Always test boundary values — the 47th number is the first that overflows int.
  • In production, instrument Fibonacci calls with a max-n check and log warnings before overflow occurs.
Production debug guideSymptom → Action guide for the three most common production issues4 entries
Symptom · 01
Memoization still runs slowly — no speed improvement over naive recursion
Fix
Check the memo array: is it being re-initialised inside each recursive call? Move the memo array to a static or method parameter and initialise once.
Symptom · 02
Negative Fibonacci values for n >= 47
Fix
Verify data type is long or BigInteger. If using int, swap to long. If using long and n > 92, switch to BigInteger.
Symptom · 03
StackOverflowError for large n (e.g., n > 10,000) in memoization
Fix
Switch from top-down memoization (recursive) to bottom-up tabulation (iterative). The recursive call stack depth equals n and will overflow for sufficiently large values.
Symptom · 04
Tabulation returns fib(0) = 1 or fib(1) = 0
Fix
Check the base case initialisation: fibTable[0] must be 0, fibTable[1] must be 1. If seeds are swapped, all subsequent values shift.
★ Fibonacci DP Debugging Quick ReferenceTwo commands and a fix for the three most common Fibonacci DP runtime failures
Slow execution (O(2^n) when expecting O(n))
Immediate action
Add a print statement inside the recursive function to count calls
Commands
System.out.println("fib(" + n + ") called");
Check memo array size and initialisation: Arrays.toString(memo)
Fix now
Move memo array outside the recursive method and initialise with -1
Negative values for n >= 47+
Immediate action
Check the return type of fib method
Commands
System.out.println("Max int = " + Integer.MAX_VALUE);
System.out.println("fib(47) = " + fib(47));
Fix now
Change return type from int to long or BigInteger
Stack overflow for n > 10,000 (memoization)+
Immediate action
Check if recursion is used; estimate recursion depth ~ n
Commands
If using recursion, convert to iterative tabulation
Alternatively, increase JVM stack size: -Xss10m
Fix now
Replace memoization with tabulation (iterative loop)
Memoization vs Tabulation vs Fast Doubling
Feature / AspectMemoization (Top-Down)Tabulation (Bottom-Up)Fast Doubling
DirectionStarts at fib(n), recurses downStarts at fib(0), loops upDivides problem in half recursively
Uses Recursion?Yes — recursive calls with a cacheNo — pure iterative loopYes — recursive divide-and-conquer
Risk of Stack Overflow?Yes — for very large n (n > ~10,000)No — no call stack usedOnly O(log n) depth — safe for huge n
Time ComplexityO(n) — each value computed onceO(n) — single forward passO(log n) — logarithmic depth
Space ComplexityO(n) — memo array + call stackO(n) table, or O(1) optimisedO(log n) recursive call stack
Ease to WriteEasy — small change from naive recursionSlightly more deliberate setupModerate — requires creative formula
Best Use CaseWhen you don't need all sub-problemsWhen you need all values or max speedWhen n is extremely large (10^6+)
Solves Only Needed Sub-problems?Yes — lazy evaluationNo — computes all values up to nNo — still computed via recurrence
Works for n > 10^6?No — stack overflow and slowToo slow — O(n) too heavyYes — O(log n) handles billion n

Key takeaways

1
Naive recursion is O(2^n) because it recomputes the same sub-problems exponentially
fib(3) alone is called 8 times inside fib(10).
2
Memoization (top-down) fixes this by caching results in an array with a -1 sentinel
cutting the time to O(n) with a minimal code change.
3
Tabulation (bottom-up) avoids recursion entirely by filling a table from index 0 upward
no stack overflow risk, and space can be reduced to O(1) with two variables.
4
For n > 92, use BigInteger. For n > 10^6, use fast doubling (O(log n)) or matrix exponentiation
O(n) DP becomes too slow.
5
Fibonacci is a gateway problem
the exact same DP thinking (identify overlapping sub-problems, store results, reuse them) scales directly to coin change, knapsack, and edit distance.

Common mistakes to avoid

4 patterns
×

Using 0 as the memo sentinel value

Symptom
fib(0) is considered cached and returns 0 correctly, but other values may be skipped or incorrectly cached if 0 is a valid result. More commonly: if memo array is filled with 0, fib(1) returns 0 instead of 1 because memo[1] is 0 from initialisation, but recursive calls for n>1 might never compute or return 0 erroneously.
Fix
Always initialise the memo array with -1 (or null for objects). Use Arrays.fill(memo, -1L) so that -1 unambiguously means 'not yet computed'.
×

Using int instead of long for Fibonacci values

Symptom
fib(47) = 2,971,215,073 exceeds int max (2,147,483,647) and overflows silently to a negative number. No exception is thrown. Downstream calculations produce wrong results or negative values.
Fix
Use long for n up to 92, and BigInteger for n > 92. Always choose the larger type unless you have guaranteed small n.
×

Creating a new memo array inside the recursive method

Symptom
Every recursive call creates a fresh memo array, so no caching occurs. The code runs O(2^n) instead of O(n). No speed improvement over naive recursion.
Fix
Initialise the memo array once in the calling method (e.g., main) and pass it as a parameter. Or use a static field initialised once. The memo array must persist across recursive calls.
×

Attempting tabulation for n values that aren't known in advance

Symptom
Tabulation requires knowing the maximum n before the loop. If you call fib(100) then later fib(50) with the same implementation, the table wastefully recomputes. This isn't an error but an inefficiency.
Fix
If you need multiple Fibonacci values at runtime, use memoization (which lazily caches) or implement an iterative generator that remembers the last computed index.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What is the time complexity of naive recursive Fibonacci and why — can y...
Q02SENIOR
What's the difference between memoization and tabulation? Which would yo...
Q03SENIOR
If I asked you to compute fib(1,000,000), what breaks in both DP approac...
Q01 of 03JUNIOR

What is the time complexity of naive recursive Fibonacci and why — can you draw the call tree for fib(5) to prove it?

ANSWER
Time complexity is O(2^n) because each call spawns two more until base cases. The call tree for fib(5) has 15 total calls: fib(5) calls fib(4) and fib(3); fib(4) calls fib(3) and fib(2); etc. The number of calls follows the recurrence T(n) = T(n-1) + T(n-2) + 1, which is O(2^n). Drawing the tree shows that fib(3) is computed twice, fib(2) three times — overlapping sub-problems cause the explosion. To prove: count leaves = fib(5) itself? Actually the number of leaf calls (base cases) equals the result (fib(5)=5). Total nodes ~ 2*leaf. So for n=50, leaf calls = 12,586,269,025 (fib(50)), total calls ~ 25 billion.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
What is the difference between memoization and dynamic programming?
02
Why does Fibonacci with plain recursion get so slow so fast?
03
When should I use memoization over tabulation in an interview?
04
Can I compute Fibonacci for n=1,000,000 with DP?
05
What's the space-optimised tabulation and when should I use it?
N
Naren Founder & Principal Engineer

20+ years shipping performance-critical code where algorithms decide the bill. Drawn from code that ran under real load.

Follow
Verified
production tested
May 23, 2026
last updated
1,554
articles · all by Naren
🔥

That's Dynamic Programming. Mark it forged?

9 min read · try the examples if you haven't

Previous
Rod Cutting Problem
10 / 15 · Dynamic Programming
Next
Subset Sum Problem