Fast Fibonacci recursion
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
The naive recursive Fibonacci runs in O(2^n) time because it recomputes the same subproblems. There are several ways to make Fibonacci fast: memoization reduces it to O(n), bottom-up iteration also runs in O(n) with O(1) space, and matrix exponentiation achieves O(log n) time. The fastest practical approach for most use cases is memoization or iteration, while matrix exponentiation is useful for extremely large values of n.
Naive Recursion (O(2^n))
Each call branches into two recursive calls, creating an exponential call tree. fib(5) computes fib(3) twice and fib(2) three times.
Memoization (O(n) Time, O(n) Space)
@lru_cache stores previously computed results. Each fib(k) is calculated exactly once, reducing time from O(2^n) to O(n). A manual dictionary approach works the same way:
Bottom-Up Iteration (O(n) Time, O(1) Space)
This is the most practical approach for most applications. It uses constant space by only keeping the two most recent values.
Matrix Exponentiation (O(log n))
The Fibonacci recurrence can be expressed as a matrix multiplication:
By using fast matrix exponentiation (repeated squaring), this computes F(n) in O(log n) multiplications:
Without numpy, a pure Python implementation:
Fast Doubling (O(log n), No Matrix)
Fast doubling uses two identities to compute F(n) from F(n/2):
- F(2k) = F(k) * [2*F(k+1) - F(k)]
- F(2k+1) = F(k)^2 + F(k+1)^2
Fast doubling has the same O(log n) complexity as matrix exponentiation but avoids matrix overhead and is simpler to implement.
Performance Comparison
| Method | Time | Space | F(50) time | F(10000) feasible? |
| Naive recursion | O(2^n) | O(n) stack | Minutes | No |
| Memoization | O(n) | O(n) | Instant | Yes |
| Iteration | O(n) | O(1) | Instant | Yes |
| Matrix exponentiation | O(log n) | O(1) | Instant | Yes |
| Fast doubling | O(log n) | O(log n) stack | Instant | Yes |
Common Pitfalls
- Hitting Python's recursion limit with memoization: Python's default recursion limit is 1000. For
fib_memo(5000), you getRecursionError. Either increase the limit withsys.setrecursionlimit()or use the iterative approach for large n. - Integer overflow in typed languages: In Java or C++,
longoverflows around F(92). UseBigIntegerin Java orunsigned long longin C++ for larger values. Python handles arbitrary-precision integers natively. - Using mutable default arguments carelessly: The manual memoization pattern
def fib(n, cache={})uses a mutable default that persists across calls, which is the desired behavior here but can cause subtle bugs in other contexts. - Forgetting that matrix exponentiation has a large constant factor: For small n (under ~1000), the overhead of matrix multiplication makes O(log n) matrix exponentiation slower than simple O(n) iteration. Use matrix methods only when n is very large.
- Confusing fast doubling with divide-and-conquer without memoization: A naive divide-and-conquer that computes
fib(n//2)twice per call is still exponential. Fast doubling works because it computes both F(k) and F(k+1) in a single recursive call, halving the problem once per step.
Summary
- Naive recursion is O(2^n) — never use it for n > 30
- Memoization (
@lru_cache) or bottom-up iteration bring it to O(n) — best for most use cases - Matrix exponentiation and fast doubling achieve O(log n) — useful for very large n or competitive programming
- Bottom-up iteration with O(1) space is the most practical general-purpose solution
- Fast doubling is the simplest O(log n) approach, avoiding matrix multiplication overhead

