Module 3: Introduction to Recursion
      
        Master the fundamentals of recursion.
        Learn how to implement and analyze recursive algorithms.
      
     
    
      What is Recursion?
      Recursion is a programming technique where a function calls itself to solve a problem. It's
        particularly useful for problems that can be broken down into smaller instances of the same problem.
      Key Components of Recursion:
      
        - Base Case: A condition that stops the recursion
 
        - Recursive Case: The function calling itself with a smaller/simpler input
 
        - Progress Toward Base Case: Ensuring the recursion will eventually terminate
 
      
      Simple Example: Factorial
      
        // Recursive factorial function
        function factorial(n) {
        // Base case
        if (n === 0 || n === 1) {
        return 1;
        }
        // Recursive case
        return n * factorial(n - 1);
        }
        // Example usage
        console.log(factorial(5)); // 120 (5 * 4 * 3 * 2 * 1)
      
      Recursive Function Tracing
      Understanding how recursive calls stack up is crucial:
      
        // Tracing factorial(5):
        factorial(5)
        → 5 * factorial(4)
        → 5 * (4 * factorial(3))
        → 5 * (4 * (3 * factorial(2)))
        → 5 * (4 * (3 * (2 * factorial(1))))
        → 5 * (4 * (3 * (2 * 1)))
        → 5 * (4 * (3 * 2))
        → 5 * (4 * 6)
        → 5 * 24
        → 120
      
     
    
      More Recursive Examples
      1. Fibonacci Sequence
      
        // Recursive Fibonacci (inefficient but illustrative)
        function fibonacci(n) {
        // Base cases
        if (n <= 0) return 0; if (n===1) return 1; // Recursive case return fibonacci(n - 1) + fibonacci(n - 2); } //
          Example usage console.log(fibonacci(6)); // 8 
          Note: The simple recursive implementation of Fibonacci has exponential time complexity O(2^n). For
            efficiency, you'd use dynamic programming or memoization:
          
            // Fibonacci with memoization
            function fibonacciMemo(n, memo = {}) {
            if (n in memo) return memo[n];
            if (n <= 0) return 0; if (n===1) return 1; memo[n]=fibonacciMemo(n - 1, memo) + fibonacciMemo(n - 2, memo);
              return memo[n]; } // Example usage console.log(fibonacciMemo(50)); // Much faster for large inputs 
              2. Linked List Recursion
              
                // Node definition
                class Node {
                constructor(value) {
                this.value = value;
                this.next = null;
                }
                }
                // Find length of linked list recursively
                function getLength(head) {
                // Base case: empty list
                if (head === null) {
                return 0;
                }
                // Recursive case: count this node plus the rest
                return 1 + getLength(head.next);
                }
                // Reverse a linked list recursively
                function reverseList(head) {
                // Base cases: empty list or only one node
                if (head === null || head.next === null) {
                return head;
                }
                // Recursive case
                const newHead = reverseList(head.next);
                // Reverse the pointers
                head.next.next = head;
                head.next = null;
                return newHead;
                }
              
           
          
            Recursion vs. Iteration
            It's important to understand when to use recursion versus iterative approaches:
            
              
                
                
                
              
              
                | Memory Usage | 
                Uses call stack (higher memory overhead) | 
                Typically uses less memory | 
              
              
                | Code Clarity | 
                Often more elegant and readable for certain problems | 
                May be more straightforward for simple problems | 
              
              
                | Performance | 
                Can be slower due to function call overhead | 
                Usually faster | 
              
              
                | Stack Overflow Risk | 
                Possible with deep recursion | 
                Not an issue | 
              
              
                | Best For | 
                Tree traversals, divide-and-conquer algorithms | 
                Simple loops, performance-critical code | 
              
            
            Iterative vs. Recursive Factorial Example
            
              // Recursive
              function factorialRecursive(n) {
              if (n === 0 || n === 1) return 1;
              return n * factorialRecursive(n - 1);
              }
              // Iterative
              function factorialIterative(n) {
              let result = 1;
              for (let i = 2; i <= n; i++) { result *=i; } return result; } 
             
            Note: Previously, this course referenced the CodeSignal Arcade for practice, which is no
              longer available. The LeetCode problems below follow the same principles and are an excellent alternative
              for practicing recursion.