Skip to content

Fibonacci Number Computation: Recursive vs Iterative Approaches

Question

Write two functions to compute the nth Fibonacci number: a) Recursive: public static int fibRecursive(int n); b) Iterative: public static int fibIterative(int n);

Then compare and contrast: - Time complexity - Space complexity - Performance and use cases of these two approaches (recursive vs. non-recursive). - You can safely assume small inputs that can fit primitive integer type (n).

Answer

Implementation

  1. Recursive Implementation

    public static int fibRecursive(int n) {
        // Input validation
        if (n < 0) {
            throw new IllegalArgumentException("Input must be non-negative");
        }
    
        // Base cases
        if (n == 0) return 0;
        if (n == 1) return 1;
    
        // Recursive case
        return fibRecursive(n - 1) + fibRecursive(n - 2);
    }
    

  2. Iterative Implementation

    public static int fibIterative(int n) {
        // Input validation
        if (n < 0) {
            throw new IllegalArgumentException("Input must be non-negative");
        }
    
        // Base cases
        if (n == 0) return 0;
        if (n == 1) return 1;
    
        // Iterative computation
        int prev = 0;
        int curr = 1;
    
        for (int i = 2; i <= n; i++) {
            int next = prev + curr;
            prev = curr;
            curr = next;
        }
    
        return curr;
    }
    

Complexity Analysis

  1. Time Complexity
  2. Recursive Approach: O(2^n)

    • Each recursive call generates two more recursive calls
    • Creates a binary tree of calls with height n
    • Total number of nodes ≈ 2^n
    • Example for n=5:
      fib(5)
      ├── fib(4)
      │   ├── fib(3)
      │   │   ├── fib(2)
      │   │   │   ├── fib(1)
      │   │   │   └── fib(0)
      │   │   └── fib(1)
      │   └── fib(2)
      │       ├── fib(1)
      │       └── fib(0)
      └── fib(3)
          ├── fib(2)
          │   ├── fib(1)
          │   └── fib(0)
          └── fib(1)
      
  3. Iterative Approach: O(n)

    • Single loop that runs n-1 times
    • Each iteration performs constant time operations
    • No redundant calculations
  4. Space Complexity

  5. Recursive Approach: O(n)

    • Stack space used by recursive calls
    • Maximum depth of recursion is n
    • Each call uses constant space
    • Total space ≈ n * constant = O(n)
  6. Iterative Approach: O(1)

    • Uses only three variables (prev, curr, next)
    • Space usage is constant regardless of input size
    • No stack space needed

Performance Comparison

  1. Execution Time
    public class FibonacciBenchmark {
        public static void main(String[] args) {
            int[] testCases = {10, 20, 30, 40};
    
            for (int n : testCases) {
                long startTime = System.nanoTime();
                int result = fibRecursive(n);
                long endTime = System.nanoTime();
                System.out.printf("Recursive fib(%d): %d, Time: %d ns%n", 
                    n, result, (endTime - startTime));
    
                startTime = System.nanoTime();
                result = fibIterative(n);
                endTime = System.nanoTime();
                System.out.printf("Iterative fib(%d): %d, Time: %d ns%n", 
                    n, result, (endTime - startTime));
            }
        }
    }
    

Expected output (approximate):

Recursive fib(10): 55, Time: 1000 ns
Iterative fib(10): 55, Time: 100 ns
Recursive fib(20): 6765, Time: 10000 ns
Iterative fib(20): 6765, Time: 200 ns
Recursive fib(30): 832040, Time: 100000 ns
Iterative fib(30): 832040, Time: 300 ns
Recursive fib(40): 102334155, Time: 1000000 ns
Iterative fib(40): 102334155, Time: 400 ns

  1. Memory Usage
  2. Recursive approach can cause stack overflow for large n
  3. Iterative approach has constant memory usage
  4. Example of stack overflow:
    try {
        fibRecursive(100); // Will throw StackOverflowError
    } catch (StackOverflowError e) {
        System.out.println("Stack overflow occurred");
    }
    

Use Cases

  1. Recursive Approach
  2. Advantages:

    • More intuitive and easier to understand
    • Matches mathematical definition closely
    • Good for small values of n (n < 40)
    • Useful in educational contexts
  3. Disadvantages:

    • Poor performance for large n
    • Risk of stack overflow
    • Redundant calculations
    • Not suitable for production use with large inputs
  4. Iterative Approach

  5. Advantages:

    • Better performance for all input sizes
    • Constant space usage
    • No risk of stack overflow
    • Suitable for production use
  6. Disadvantages:

    • Slightly more complex to understand
    • Less intuitive than recursive solution
    • Requires careful variable management

Best Practices

  1. Input Validation

    public static int fibIterative(int n) {
        if (n < 0) {
            throw new IllegalArgumentException("Input must be non-negative");
        }
        if (n > 46) { // Maximum value that fits in int
            throw new IllegalArgumentException("Result would overflow int");
        }
        // ... rest of the implementation
    }
    

  2. Performance Optimization

    public static int fibIterative(int n) {
        // Use lookup table for small values
        if (n <= 1) return n;
    
        // Use long to prevent overflow
        long prev = 0;
        long curr = 1;
    
        for (int i = 2; i <= n; i++) {
            long next = prev + curr;
            if (next > Integer.MAX_VALUE) {
                throw new ArithmeticException("Result would overflow int");
            }
            prev = curr;
            curr = next;
        }
    
        return (int)curr;
    }
    

Testing

public class FibonacciTest {
    @Test
    public void testFibonacci() {
        // Test base cases
        assertEquals(0, fibRecursive(0));
        assertEquals(0, fibIterative(0));
        assertEquals(1, fibRecursive(1));
        assertEquals(1, fibIterative(1));

        // Test small values
        assertEquals(1, fibRecursive(2));
        assertEquals(1, fibIterative(2));
        assertEquals(2, fibRecursive(3));
        assertEquals(2, fibIterative(3));

        // Test larger values
        assertEquals(55, fibRecursive(10));
        assertEquals(55, fibIterative(10));

        // Test edge cases
        assertThrows(IllegalArgumentException.class, () -> fibRecursive(-1));
        assertThrows(IllegalArgumentException.class, () -> fibIterative(-1));
    }
}

Conclusion

  • For small values of n (n < 40), both approaches are viable
  • For larger values or production use, the iterative approach is recommended
  • The recursive approach is better for educational purposes and small inputs
  • The iterative approach is better for performance and memory efficiency
  • Both implementations should include proper input validation and overflow checking