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)
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.
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
publicclassNaiveFibonacci {
/**
* 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.
*/
publicstaticlongfib(int n) {
// Base cases: fib(0) = 0, fib(1) = 1 — the sequence's starting seedsif (n == 0) return0;
if (n == 1) return1;
// Every call spawns TWO more calls — this is where the explosion happensreturnfib(n - 1) + fib(n - 2);
}
publicstaticvoidmain(String[] args) {
// These small values are fine — the tree is tinySystem.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 minutesSystem.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;
publicclassMemoizedFibonacci {
/**
* Top-DownDynamicProgramming — Memoization.
* 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.
*/
publicstaticlongfib(int n, long[] memo) {
// Base cases — the two seeds every Fibonacci sequence starts fromif (n == 0) return0;
if (n == 1) return1;
// CHECK THE NOTEBOOK FIRST — did we already compute this?// memo[n] != -1 means we've been here before and stored the answerif (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;
}
publicstaticvoidmain(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 = newlong[target + 1];
Arrays.fill(memo, -1);
System.out.println("=== Memoized Fibonacci ===");
// These will all be instant — even fib(50) completes in microsecondsfor (int i = 0; i <= 10; i++) {
// Reuse the same memo table — values computed earlier help later callsSystem.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
publicclassTabulatedFibonacci {
/**
* Bottom-UpDynamicProgramming — Tabulation.
* 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.
*/
publicstaticlongfibTableFull(int n) {
if (n == 0) return0;
if (n == 1) return1;
// Create a table where index i will hold the value of fib(i)long[] fibTable = newlong[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 cellsfor (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 tablereturn fibTable[n];
}
/**
* Space-OptimisedTabulation — O(1) space.
* We only ever need the PREVIOUSTWO 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.
*/
publicstaticlongfibSpaceOptimised(int n) {
if (n == 0) return0;
if (n == 1) return1;
long previousPrevious = 0; // Represents fib(i-2)
long previous = 1; // Represents fib(i-1)
long current = 0; // Will hold fib(i) each iterationfor (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;
}
publicstaticvoidmain(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.
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?"
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;
publicclassFastDoublingFibonacci {
/**
* Returnsfib(n) using fast doubling method, O(log n).
* Returns a pair (fib(n), fib(n+1)).
*/
publicstaticBigInteger[] fib(int n) {
if (n == 0) returnnewBigInteger[] { 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*bBigInteger d = a.multiply(a).add(b.multiply(b));
if ((n & 1) == 0) {
returnnewBigInteger[] { c, d };
} else {
returnnewBigInteger[] { d, c.add(d) };
}
}
publicstaticvoidmain(String[] args) {
int n = 1000000; // one millionBigInteger 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.
● 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 / Aspect
Memoization (Top-Down)
Tabulation (Bottom-Up)
Fast Doubling
Direction
Starts at fib(n), recurses down
Starts at fib(0), loops up
Divides problem in half recursively
Uses Recursion?
Yes — recursive calls with a cache
No — pure iterative loop
Yes — recursive divide-and-conquer
Risk of Stack Overflow?
Yes — for very large n (n > ~10,000)
No — no call stack used
Only O(log n) depth — safe for huge n
Time Complexity
O(n) — each value computed once
O(n) — single forward pass
O(log n) — logarithmic depth
Space Complexity
O(n) — memo array + call stack
O(n) table, or O(1) optimised
O(log n) recursive call stack
Ease to Write
Easy — small change from naive recursion
Slightly more deliberate setup
Moderate — requires creative formula
Best Use Case
When you don't need all sub-problems
When you need all values or max speed
When n is extremely large (10^6+)
Solves Only Needed Sub-problems?
Yes — lazy evaluation
No — computes all values up to n
No — still computed via recurrence
Works for n > 10^6?
No — stack overflow and slow
Too slow — O(n) too heavy
Yes — 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.
Q02 of 03SENIOR
What's the difference between memoization and tabulation? Which would you choose and why — give a concrete scenario where one beats the other.
ANSWER
Memoization is top-down: start from the goal and recurse down with caching. Tabulation is bottom-up: fill a table iteratively from the smallest sub-problems upward.
Choose memoization when: the solution is naturally recursive, you only need some sub-problems (lazy evaluation), or the problem has pruned branches. Example: computing Fibonacci for a single large n when you already have the recursion.
Choose tabulation when: you need all sub-problems (always true for Fibonacci), n is large (avoid stack overflow), memory is tight (can optimise to O(1)), or you want deterministic performance with no recursion overhead.
Concrete scenario: In a real-time trading system where Fibonacci is used for retracement levels, tabulation is safer because recursion could cause unpredictable latency and stack growth. The O(1) space optimised version is ideal.
Q03 of 03SENIOR
If I asked you to compute fib(1,000,000), what breaks in both DP approaches and how would you fix each issue? (Hint: think stack overflow for memoization and integer overflow for both.)
ANSWER
Memoization breaks: Stack overflows at recursion depth ~10,000 on JVM. Fix: increase stack size (-Xss) or convert to tabulation.
Tabulation breaks: Long overflow at fib(92). Fix: use BigInteger. But also, O(n) = 1,000,000 iterations is fine (under a second). However, for n=10^9, O(n) becomes impractical (billions of operations). Fix for extremely large n: use fast doubling method (O(log n) time) with BigInteger.
Both share the problem that the result has ~208,987 digits for n=1,000,000. Storing and adding such large BigIntegers takes time. Fix: use fast doubling which halves the problem size logarithmically, reducing the number of BigInteger operations to O(log n).
01
What is the time complexity of naive recursive Fibonacci and why — can you draw the call tree for fib(5) to prove it?
JUNIOR
02
What's the difference between memoization and tabulation? Which would you choose and why — give a concrete scenario where one beats the other.
SENIOR
03
If I asked you to compute fib(1,000,000), what breaks in both DP approaches and how would you fix each issue? (Hint: think stack overflow for memoization and integer overflow for both.)
SENIOR
FAQ · 5 QUESTIONS
Frequently Asked Questions
01
What is the difference between memoization and dynamic programming?
Memoization is one technique within dynamic programming. DP is the broader strategy of solving problems by breaking them into overlapping sub-problems and storing results to avoid recomputation. Memoization achieves this top-down using recursion plus a cache; tabulation achieves it bottom-up using an iterative loop and a table. Both are valid DP approaches.
Was this helpful?
02
Why does Fibonacci with plain recursion get so slow so fast?
Because every call to fib(n) makes two more calls, the number of total calls grows as 2^n — doubling for every single step you add. At n=50 that's over a quadrillion operations. The root cause is that identical sub-problems like fib(10) are recomputed from scratch every time they appear in the tree instead of being looked up from a cache.
Was this helpful?
03
When should I use memoization over tabulation in an interview?
Use memoization when the solution feels naturally recursive (you think top-down), when you only need some sub-problems computed (sparse access patterns), or when the interviewer wants to see recursive thinking. Use tabulation when they ask about space efficiency, when n could be very large (stack overflow risk), or when you need to print all Fibonacci values up to n. In practice, tabulation is safer for production code.
Was this helpful?
04
Can I compute Fibonacci for n=1,000,000 with DP?
Yes, with BigInteger and either tabulation or fast doubling. Tabulation will take about 1 million iterations — each iteration does BigInteger addition, which is fast (about 1 microsecond per addition at that size). That would take roughly 1 second. Fast doubling uses O(log n) recursions (about 20 for n=1,000,000) and is faster overall. The result has ~208,987 digits.
Was this helpful?
05
What's the space-optimised tabulation and when should I use it?
Space-optimised tabulation uses only two variables instead of an array of size n. It's ideal when memory is constrained (e.g., embedded systems) and you only need the final value. The trade-off is you can't reuse intermediate values later.