Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Iterate bottom-up keeping only the last two values for O(n) time and O(1) space. When n is huge, use fast doubling (or matrix exponentiation) to compute F(n) in O(log n) via the identities F(2k) = F(k)·(2F(k+1) − F(k)) and F(2k+1) = F(k)² + F(k+1)².
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you recognize naive recursion is exponential, reach for the O(1)-space iterative form, and — for the medium bar — know that an O(log n) method exists and why.
Where candidates slip
Submitting exponential recursion, getting the F(0)/F(1) base case off by one, or overflowing a long around F(93) without noticing.
How to stand out
Walk the full ladder: exponential recursion → memoized O(n) → iterative O(n) O(1) space → fast doubling O(log n). State the overflow point and offer BigInteger.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewCompute F(n)How big is n?tiny / interviewhugeExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Iterative Bottom-Up, Two Variables (Baseline)
1Hint 1Explore hintHide hint
Naive recursion recomputes fib(n-2) over and over. What's the minimum history you actually need to produce the next term?
2Hint 2Explore hintHide hint
Just the previous two values. Carry a = F(i-1), b = F(i), and roll them forward n times.
3Hint 3Explore hintHide hint
Start a, b = 0, 1 and repeat a, b = b, a + b. After n rolls, a holds F(n).
A · Core idea
Build the sequence from the bottom, carrying only the two most recent values. Seed a = F(0) = 0 and b = F(1) = 1, then repeat (a, b) = (b, a + b) exactly n times; a then holds F(n).
Why naive recursion is a trap. Writing fib(n) = fib(n-1) + fib(n-2) looks like the definition, but it rebuilds the same subtrees over and over — fib(n-2) is computed twice, fib(n-3) three times, and so on. The call count grows like φⁿ (golden-ratio base), so fib(50) already takes seconds. Mention it only to dismiss it.
Why two variables suffice. The recurrence reaches back exactly two terms. Once you've produced F(i), you never need anything older than F(i-1) again. So a full dp[] array is wasted memory — two rolling scalars capture all the state, dropping space from O(n) to O(1). This 'roll the window' move generalizes to any fixed-depth linear recurrence.
Why the loop runs n times and a is the answer. Start (a, b) as (F(0), F(1)). Each roll advances the window by one index: after one roll a = F(1), after two a = F(2), …, after n rolls a = F(n). Returning a (not b) is the off-by-one most people get wrong — trace the first couple of iterations to be sure.
Why overflow still bites. Fibonacci grows exponentially (~`φⁿ), so it blows past a 64-bit longatF(93). The loop is correct, but the *type* must widen to BigIntegerfor largen`. In Python this is automatic; in Java, say it out loud.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
n = 6Array state
roll 2
(a, b) = (1, 2)
8C · Code with teaching comments
Reference implementation previewpublic long fib(int n) { if (n < 0) { throw new IllegalArgumentException("n must be non-negative"); } long a = 0, b = 1;Explore the full codeHide the full code
D · Why it works and complexityO(n) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
You only need the last two terms, so the natural-looking DP array collapses to two variables and O(1) space. That window-rolling instinct — keep only what the recurrence references — is the reusable idea, not Fibonacci itself.
ComplexityO(n) time, O(1) space — One loop of n iterations, each doing a constant-time addition and reassignment. Only two scalars are stored, so space is O(1) — no DP array needed.
E · Edge cases and pitfalls3Submitting exponential naive recursion.Explore edge casesHide edge cases
Pitfalls
- Submitting exponential naive recursion.
- Returning b instead of a (off-by-one — b is F(n+1)).
- Overflowing a long at F(93) without noticing.
Approach 2 of 2 · Optimal
Fast Doubling (Optimal, O(log n))
1Hint 1Explore hintHide hint
The iterative method does n additions. Could you jump from F(k) straight to F(2k), halving the index each step like fast exponentiation?
2Hint 2Explore hintHide hint
Yes — there are identities expressing F(2k) and F(2k+1) purely in terms of F(k) and F(k+1).
3Hint 3Explore hintHide hint
Recurse on n >> 1 (n / 2), get (F(k), F(k+1)), then combine: F(2k) = F(k)·(2F(k+1) − F(k)), F(2k+1) = F(k)² + F(k+1)². Pick the right pair based on the low bit of n.
A · Core idea
Compute F(n) by halving the index instead of decrementing it — the same trick that turns exponentiation into O(log n). Each level computes (F(k), F(k+1)) from (F(k/2), F(k/2 + 1)) using two closed-form identities.
The identities and why they work. From the matrix form of Fibonacci you can derive:
F(2k) = F(k) · (2·F(k+1) − F(k))F(2k+1) = F(k)² + F(k+1)²
Given the pair at index k, these produce the pair at index 2k in a constant number of multiplications. Because doubling the index each step covers n in log₂ n doublings, the whole computation is logarithmic.
Why we track a *pair*, not a single value. Both identities need F(k) and F(k+1) to step to the next level. So the recursion returns the tuple (F(k), F(k+1)); at each return we compute the doubled pair and then, if the current bit of n is 1, shift by one more (the (F(2k+1), F(2k) + F(2k+1)) adjustment) to land on the correct index. Read n bit by bit from the most significant end.
Why it beats matrix exponentiation in practice. Matrix exponentiation raises [[1,1],[1,0]] to the n-th power, also O(log n), but each step does eight scalar multiplies for the 2×2 product. Fast doubling does just a few multiplies per level for the same asymptotic bound, so it wins on constant factors and is less error-prone to code. Mention both; prefer doubling.
Why O(log n) is a refinement, not free. For large n the numbers themselves are huge (BigInteger), so each multiply isn't truly O(1). The honest claim is O(log n) arithmetic operations; the bit-complexity is higher. Still vastly better than O(n) additions on giant numbers.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
n = 6 (binary 110)Interactive diagram
Halving the index each level
n=0Fast doubling processes the bits of n, so depth is log₂ n rather than n.
8C · Code with teaching comments
D · Why it works and complexityO(log n) timeO(log n) recursion stack spaceOpen the invariant and proofExplore the proofHide the proof
Fibonacci satisfies a doubling recurrence, so the same divide-the-exponent strategy behind fast power applies to the index. Recognizing that a linear recurrence has a logarithmic shortcut — via matrix power or these identities — is the medium-level signal.
ComplexityO(log n) time, O(log n) recursion stack space — Each recursive call halves n (processes one bit), so there are O(log n) calls; each does a constant number of multiplications. With fixed-width ints those multiplies are O(1), giving O(log n); with BigInteger the multiplies grow, but the call count is still log n.
E · Edge cases and pitfalls6n = 0 — Base case returns the pair (0, 1); fib returns 0.Explore edge casesHide edge cases
Edge cases handled
n = 0Base case returns the pair (0, 1); fib returns 0.n = 1Recurses to n=0, doubles+shifts to (1, 1); returns 1.n = 93Exceeds a long; BigInteger returns the exact 12200160415121876738.
Pitfalls
- Mixing up which identity is F(2k) vs F(2k+1).
- Forgetting the odd-bit shift, landing one index off.
- Claiming flat O(log n) for big n — the BigInteger multiplies themselves grow.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewimport java.math.BigInteger; public BigInteger fib(int n) { return fibPair(n)[0]; }Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Exponential naive recursion
fib(n-1) + fib(n-2) recomputes overlapping subproblems, giving O(φⁿ) calls. Memoize or iterate.
Explore the code comparisonHide the code comparison
Exponential naive recursion
fib(n-1) + fib(n-2) recomputes overlapping subproblems, giving O(φⁿ) calls. Memoize or iterate.
Don't write this
def fib(self, n: int) -> int:
if n < 2:
return n
return self.fib(n - 1) + self.fib(n - 2) # exponentialWrite this instead
def fib(self, n: int) -> int:
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return a2Off-by-one on the return value
After n rolls of (a, b) = (b, a+b), a holds F(n) and b holds F(n+1). Returning b shifts every answer up by one.
Explore the code comparisonHide the code comparison
Off-by-one on the return value
After n rolls of (a, b) = (b, a+b), a holds F(n) and b holds F(n+1). Returning b shifts every answer up by one.
Don't write this
long a = 0, b = 1;
for (int i = 0; i < n; i++) {
long next = a + b; a = b; b = next;
}
return b; // this is F(n+1)Write this instead
long a = 0, b = 1;
for (int i = 0; i < n; i++) {
long next = a + b; a = b; b = next;
}
return a;3Silent long overflow
F(93) exceeds Long.MAX_VALUE, so the addition wraps to a wrong value. Use BigInteger when n can exceed 92.
Explore the code comparisonHide the code comparison
Silent long overflow
F(93) exceeds Long.MAX_VALUE, so the addition wraps to a wrong value. Use BigInteger when n can exceed 92.
Don't write this
long a = 0, b = 1; // wrong for n >= 93
for (int i = 0; i < n; i++) { long t = a + b; a = b; b = t; }
return a;Write this instead
BigInteger a = BigInteger.ZERO, b = BigInteger.ONE;
for (int i = 0; i < n; i++) { BigInteger t = a.add(b); a = b; b = t; }
return a;Revise
Carry the pattern into the interview
Pattern to remember
Bottom-up two-variable iteration; fast doubling for O(log n)
Code pattern to memorize
# O(n) iterative
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return a
# O(log n) fast doubling identities
F(2k) = F(k) * (2*F(k+1) - F(k))
F(2k+1) = F(k)^2 + F(k+1)^2Rules to remember
- 1Never write naive recursion
fib(n-1) + fib(n-2)— it's O(φⁿ) and recomputes the same values exponentially. Memoize or iterate. - 2Pin down the indexing convention: this page uses F(0) = 0, F(1) = 1. Off-by-one here flips every answer.
- 3Fibonacci overflows fast — F(93) exceeds a signed 64-bit
long. Use BigInteger (Java) for large n; Python ints are unbounded. - 4The iterative version needs only two rolling variables, not a full DP array — O(1) space.
- 5Fast doubling beats matrix exponentiation in constant factors (no 2×2 multiplies) while sharing the same O(log n) bound.
Use this pattern when
- Compute a single F(n) — iterative O(n) is the clean default
- n is astronomically large and you need O(log n) → fast doubling / matrix power
- Teaching the memoization-vs-tabulation progression in DP
- Any linear recurrence where you only need the last few terms (roll the window)
Choose another pattern when
- You need *all* F(0..n) → keep the array; don't recompute each
- n is tiny and fixed → a lookup table is simplest
- You need F(n) mod m for huge n → fast doubling under the modulus (Pisano period optional)
Interview takeaway
Fibonacci is the canonical 'naive recursion is exponential, but it collapses to O(n) with two variables and O(log n) with doubling' story. Showing that full ladder — and naming the overflow boundary — is what an interviewer is grading.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternLinear recurrence solved bottom-up (window rolling for O(1) space) and via index-halving identities for O(log n). The transferable instincts: collapse a DP array to the few terms the recurrence references, and recognize that linear recurrences have logarithmic shortcuts through matrix power or doubling.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Climbing StairsThe number of ways to climb n stairs taking 1 or 2 steps is exactly F(n+1) — same recurrence.
- Fibonacci modulo m (Pisano period)F(n) mod m is periodic; combine fast doubling under the modulus with the Pisano period for enormous n.
- Tribonacci / k-bonacciRoll a window of the last k values; matrix exponentiation generalizes the O(log n) idea.