ЁЯФм
ЁЯзм
ЁЯФн
ЁЯкР
ЁЯзк
тЖР Back to Dashboard
Font Size:

1. Introduction

Recursion is a programming technique in which a function calls itself to solve a problem. A recursive solution expresses a problem in terms of smaller versions of the same problem, together with a simple case that stops the calling chain. The two essential ingredients are a base case, which can be solved directly without recursion, and a recursive case, which reduces the problem towards the base case.

Recursion often produces short, elegant and readable solutions for problems that are naturally defined in terms of themselves, such as computing factorials, Fibonacci numbers, powers, and the Fibonacci-like structures of trees and fractals. The mathematical definition of the factorial, n! = n x (n-1)!, is literally a recursive definition, and translating it into a function is straightforward.

This chapter explains how recursion works, the roles of the base case and recursive case, how to trace a recursive function, the common recursive algorithms of factorial, Fibonacci, power and sum of digits, and the concept of the call stack. It also contrasts recursion with iteration and discusses the efficiency and limits of recursion, such as the recursion depth limit. Recursion questions appear as output tracing, code-writing and concept questions in the board examination.

2. The Anatomy of a Recursive Function

A recursive function has two parts. The base case is a condition under which the function returns a value directly without calling itself; without it, the recursion never ends. The recursive case calls the function again with a smaller or simpler input, moving towards the base case.

def factorial(n):
    if n == 0:            # base case
        return 1
    return n * factorial(n - 1)   # recursive case

print(factorial(5))       # 120

For factorial(5), the function computes 5 x factorial(4), which is 5 x (4 x factorial(3)), and so on until factorial(0) returns 1. Only then do the multiplications unwind to produce 120.

2.1 Why the Base Case Matters

If the base case is missing or unreachable, the function keeps calling itself forever. Python detects this and raises a RecursionError after a certain number of calls (the recursion limit, by default about 1000). Every recursive function must guarantee progress toward its base case.

3. The Call Stack and How Recursion Works

Each call to a recursive function creates a new frame on the call stack, holding its own parameters and local variables. The frames accumulate as the recursion descends, and they are removed as each call returns. This is the same stack data structure studied in earlier chapters. Because each frame occupies memory, deep recursion can exhaust the stack.

Tracing factorial(4) step by step:

factorial(4) = 4 * factorial(3)
factorial(3) = 3 * factorial(2)
factorial(2) = 2 * factorial(1)
factorial(1) = 1 * factorial(0)
factorial(0) = 1                     base case reached
factorial(1) = 1 * 1 = 1
factorial(2) = 2 * 1 = 2
factorial(3) = 3 * 2 = 6
factorial(4) = 4 * 6 = 24

The calls go down until the base case, then the returns unwind in reverse order.

4. Common Recursive Algorithms

4.1 Fibonacci Series

The Fibonacci sequence is defined as fib(0) = 0, fib(1) = 1, and fib(n) = fib(n-1) + fib(n-2) for n > 1. The recursive version follows the definition directly but recomputes many values, giving exponential time in the worst case.

def fib(n):
    if n <= 1:            # base cases
        return n
    return fib(n - 1) + fib(n - 2)   # recursive case

for i in range(7):
    print(fib(i), end=" ")    # 0 1 1 2 3 5 8

4.2 Power (Exponentiation)

Computing x raised to the power n recursively: x^0 = 1, and x^n = x * x^(n-1).

def power(x, n):
    if n == 0:
        return 1
    return x * power(x, n - 1)

print(power(2, 5))    # 32

4.3 Sum of Digits

def sum_digits(n):
    if n == 0:
        return 0
    return n % 10 + sum_digits(n // 10)

print(sum_digits(1234))   # 10

The function adds the last digit to the recursive sum of the remaining digits.

4.4 Reverse of a String

def reverse_string(s):
    if s == "":
        return ""
    return reverse_string(s[1:]) + s[0]

print(reverse_string("PYTHON"))   # NOHTYP

4.5 Greatest Common Divisor (Euclid's Algorithm)

def gcd(a, b):
    if b == 0:
        return a
    return gcd(b, a % b)

print(gcd(48, 18))    # 6

5. Recursion vs Iteration

Any problem solved recursively can also be solved with a loop. Iteration uses explicit loops and an accumulator; recursion uses function calls and the call stack. Recursive code is often shorter and closer to the mathematical definition, while iterative code is usually faster and uses constant extra memory. The choice depends on readability, efficiency and the nature of the problem.

def factorial_iterative(n):
    result = 1
    for i in range(1, n + 1):
        result *= i
    return result

print(factorial_iterative(5))   # 120

Both versions produce the same answer, but the iterative one avoids deep call stacks.

6. Efficiency Concerns

Recursive solutions can be inefficient. The naive Fibonacci recursion recomputes fib(k) many times for the same k, leading to exponential time O(2^n). Techniques such as memoization (storing already-computed results) convert such recursion to near-linear time. Every recursive function should be examined for wasted recomputation.

memo = {0: 0, 1: 1}

def fib_memo(n):
    if n not in memo:
        memo[n] = fib_memo(n - 1) + fib_memo(n - 2)
    return memo[n]

print(fib_memo(30))   # 832040, computed quickly

7. Recursion Depth Limit

Python limits the depth of recursion to prevent the call stack from overflowing. The default recursion limit is usually 1000; functions can check and change it with sys.getrecursionlimit() and sys.setrecursionlimit(). Exceeding the limit raises RecursionError. Problems requiring very deep recursion are better solved iteratively.

import sys
print(sys.getrecursionlimit())   # typically 1000

Quick Revision Tables

Table 1: Components of a Recursive Function

Component Role
Base case Stops recursion; solves the smallest instance directly
Recursive case Calls the function with a smaller input
Progress Each call must move towards the base case
Call stack Stores each pending call's frame

Table 2: Common Recursive Algorithms

Algorithm Base Case Recurrence
Factorial factorial(0) = 1 n * factorial(n-1)
Fibonacci fib(0)=0, fib(1)=1 fib(n-1) + fib(n-2)
Power x^0 = 1 x * x^(n-1)
GCD gcd(a,0) = a gcd(b, a % b)

Mind Map

flowchart TD A[Recursion] --> B[Definition] B --> B1[Function calls itself] B --> B2[Base case and recursive case] A --> C[Call Stack] C --> C1[Frames pushed on call] C --> C2[Frames popped on return] A --> D[Algorithms] D --> D1[Factorial] D --> D2[Fibonacci] D --> D3[Power] D --> D4[Sum of digits] D --> D5[GCD Euclid] D --> D6[Reverse string] A --> E[Recursion vs Iteration] E --> E1[Iteration faster constant space] E --> E2[Recursion concise mathematical] A --> F[Efficiency] F --> F1[Naive Fibonacci exponential] F --> F2[Memoization] F --> F3[Recursion depth limit]

Important Diagrams (SVG)

Diagram 1: Tracing Factorial(4)

Tracing factorial(4) CALLING (goes down) RETURNING (unwinds) factorial(4) = 4 * factorial(3) factorial(3) = 3 * factorial(2) factorial(2) = 2 * factorial(1) factorial(1) = 1 * factorial(0) factorial(0) = 1 BASE CASE returns 1 returns 1 * 1 = 1 returns 2 * 1 = 2 returns 3 * 2 = 6 returns 4 * 6 = 24 Golden Rule: Calls descend to the base case; returns unwind to compute the final answer.

Diagram 2: Recursive Fibonacci Tree

Recursive Tree of fib(5) fib(5) fib(4) fib(3) fib(3) fib(2) fib(2) fib(1) recomputed recomputed recomputed fib(3) and fib(2) are computed multiple times, causing exponential O(2^n) time. Golden Rule: Avoid redundant recomputation in recursion; use memoization to store results.

Common Mistakes

  1. Missing the base case: A recursive function without a base case calls itself forever, eventually raising RecursionError.
  2. An unreachable base case: If the recursive call does not reduce the input, the base case is never reached.
  3. Wrong recurrence: Using n + fib(n-1) instead of the sum of the two previous terms in Fibonacci gives wrong output.
  4. Forgetting the stopping condition in traces: Traces that descend but never show the base case miss the moment the recursion turns around.
  5. Comparing recursion and iteration incorrectly: Claiming recursion is always faster; iteration is generally faster and uses less memory.
  6. Confusing base and recursive case roles: Returning n directly instead of applying the recurrence in the recursive case gives wrong results.
  7. Ignoring the recursion depth limit: Very deep recursion exceeds Python's default limit and raises RecursionError.
  8. Not handling negative input: Functions like factorial or sum_digits need to handle n < 0 or the recursion never terminates.

Exam Tips

  1. Always identify the base case and recursive case first when writing or tracing a recursive function.
  2. Practise full traces of factorial and Fibonacci, showing the descending calls and the returning values.
  3. Know the Fibonacci definition exactly: fib(0)=0, fib(1)=1, fib(n)=fib(n-1)+fib(n-2).
  4. State the stack behaviour: each call pushes a frame; each return pops it.
  5. Mention the efficiency problem of naive Fibonacci and the fix of memoization.
  6. Write recursive functions for standard problems like factorial, power, sum of digits and GCD.
  7. Remember the depth limit: default around 1000, adjustable with sys.setrecursionlimit().

Conclusion

Recursion is a powerful and elegant technique in which a function solves a problem by calling itself on smaller instances. Every recursive function rests on a base case that stops the chain and a recursive case that moves towards it, all tracked by the call stack. The classic algorithms of factorial, Fibonacci, power, sum of digits and GCD demonstrate the pattern clearly, though naive recursion can be inefficient, as the exponential Fibonacci computation shows. Compared with iteration, recursion trades efficiency for clarity and is best applied to problems that are naturally recursive. The final chapter closes the course by studying the idea of efficiency, formalising the complexity analysis that recursion and all other algorithms require.