Introduction to Recursion
Introduction to Recursion
Introduction to Recursion
Recursion is a programming technique where a function calls itself to solve a problem. It breaks
the problem into smaller sub problems of the same type until it reaches a base case, which stops
the recursion.
Key Concepts
Base Case: The condition that terminates the recursion to prevent infinite loops.
Recursive Case: The part where the function calls itself to solve a smaller version of the
problem.
def. factorial(n):
if n == 0: # Base case
return 1
Useful for problems like tree traversal, graph algorithms, and divide-and-conquer
approaches (e.g., Merge Sort, Quick Sort).
if n == 0: # Base case
1
RECURSION
return
print("Hello, Recursion!")
print_message(3)
Output:
Hello, Recursion!
Hello, Recursion!
Hello, Recursion!
F(0)=0F(0) = 0
F(1)=1F(1) = 1
def fibonacci(n):
if n == 0: # Base case 1
return 0
if n == 1: # Base case 2
return 1
print(fibonacci(6)) # Output: 8
Recursion is widely used in tree structures, such as binary trees. Here's an example of a simple
recursive in-order traversal.
2
RECURSION
class Node:
self.value = value
self.left = None
self.right = None
def in_order_traversal(root):
if root is None:
return
root = Node(1)
root.left = Node(2)
root.right = Node(3)
root.left.left = Node(4)
root.left.right = Node(5)
in_order_traversal(root) # Output: 4 2 5 1 3
3
RECURSION
def print_message(n):
if n == 0: # Base case
return
print("Hello, Recursion!")
print_message(3)
Output:
Hello, Recursion!
Hello, Recursion!
Hello, Recursion!
F(0)=0F(0) = 0
F(1)=1F(1) = 1
def fibonacci(n):
if n == 0: # Base case 1
return 0
if n == 1: # Base case 2
return 1
4
RECURSION
print(fibonacci(6)) # Output: 8
Recursion is widely used in tree structures, such as binary trees. Here's an example of a simple
recursive in-order traversal.
class Node:
self.value = value
self.left = None
self.right = None
def in_order_traversal(root):
if root is None:
return
root = Node(1)
root.left = Node(2)
root.right = Node(3)
root.left.left = Node(4)
root.left.right = Node(5)
in_order_traversal(root) # Output: 4 2 5 1 3
5
RECURSION
Recursion is a fundamental concept where a function calls itself to break down a problem into
smaller subproblems. To understand recursion deeply, let's go through its syntax, examples, and
how it works internally using the call stack.
def recursive_function(parameters):
return result
Key Components:
1. Function Definition: The function is defined normally, just like any other function.
2. Base Case: A condition that stops the recursion to prevent infinite loops.
3. Recursive Case: The function calls itself with modified parameters to approach the base
case.
0!=10! = 1
6
RECURSION
def factorial(n):
if n == 0: # Base case
return 1
F(0)=0F(0) = 0
F(1)=1F(1) = 1
def fibonacci(n):
if n == 0: # Base case 1
return 0
if n == 1: # Base case 2
return 1
print(fibonacci(6)) # Output: 8
When a recursive function is called, each function call is stored in the call stack until it reaches
the base case. The stack follows Last In, First Out (LIFO) order.
factorial(3)
→ factorial(2)
→ factorial(1)
7
RECURSION
← factorial(1) returns 1 * 1 = 1
← factorial(2) returns 2 * 1 = 2
← factorial(3) returns 3 * 2 = 6
factorial(3) 3*2=6
factorial(2) 2*1=2
factorial(1) 1*1=1
Each recursive call stores its state in the stack until the base case is reached. Once the base case
returns a value, the stored states start resolving in reverse order.
Conclusion
Recursive functions consist of a base case (stopping condition) and a recursive case
(function calling itself).
The call stack stores each recursive function call and unwinds after reaching the base
case.
Recursion is useful in solving problems like factorial, Fibonacci, tree traversal, and
divide-and-conquer algorithms.
Recursion can sometimes be hard to follow, especially when multiple recursive calls are
happening. Debugging helps visualize how function calls are made, how they return values, and
how the call stack behaves.
The simplest way to debug recursion is by adding print statements to track when a function is
called and when it returns a value.
def factorial(n):
if n == 0: # Base case
8
RECURSION
return 1
return result
factorial(4)
Recursion is powerful for solving complex problems, especially when combined with strategies
like divide and conquer and dynamic programming. It is widely used in sorting algorithms,
optimization problems, and real-world applications like parsing and file system traversal.
Divide and conquer breaks a problem into smaller subproblems, solves them recursively, and
then combines the results.
Merge Sort splits an array into halves, recursively sorts each half, and then merges them.
def merge_sort(arr):
return arr
mid = len(arr) // 2
sorted_arr = []
i=j=0
9
RECURSION
sorted_arr.append(left[i])
i += 1
else:
sorted_arr.append(right[j])
j += 1
sorted_arr.extend(left[i:])
sorted_arr.extend(right[j:])
return sorted_arr
arr = [5, 3, 8, 6, 2, 7, 4, 1]
✅ Why Recursion?
Quick Sort picks a pivot, partitions the array into smaller/larger elements, and recursively sorts
them.
def quick_sort(arr):
return arr
pivot = arr[len(arr) // 2]
10
RECURSION
arr = [10, 7, 8, 9, 1, 5]
✅ Why Recursion?
Recursion can become inefficient if it repeatedly solves the same subproblems. Memoization
stores results to avoid redundant calls.
return memo[n]
if n <= 1:
return n
return memo[n]
print(fibonacci(10)) # Output: 55
✅ Why Memoization?
11
RECURSION
def evaluate(expression):
if expression.isdigit():
return int(expression)
start = expression.rfind("(")
result = evaluate(expression[start+1:end])
tokens = expression.split()
✅ Why Recursion?
Recursively listing all files in a directory (like how OS file explorers work).
import os
def list_files(directory):
12
RECURSION
list_files(path)
else:
Why Recursion?
in file manage
Recursion is powerful but can lead to inefficiencies like redundant calculations and high memory
usage. Understanding optimization techniques, recursion limits, and when recursion isn't ideal
is crucial.
1. Optimizing Recursion
Recursive functions can be inefficient when they recompute values multiple times or use
excessive stack space. Optimizing recursion improves performance and avoids crashes.
if n in memo:
return memo[n]
if n <= 1:
return n
13
RECURSION
return memo[n]
Some languages support Tail Recursion Optimization (TRO), but Python does not
natively.
if n == 0:
return accumulator
Why ?
The recursive call is the last operation, so it doesn’t need extra stack frames.
Python does not optimize tail recursion, but other languages like Scheme or JavaScript
(with proper flags) do.
def fibonacci_iterative(n):
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return a
14
RECURSION
print(fibonacci_iterative(50))
Why?
Uses constant memory (O(1)), unlike recursion, which has O(n) stack space.
Python has a default recursion depth limit of 1000 calls. Going beyond this causes a
RecursionError.
import sys
⚠️Warning: Increasing too much may crash your program due to memory exhaustion.
3. Limitations of Recursion
Recursive calls consume memory for each function call, leading to stack overflow in
deep recursion (e.g., computing factorial(10000) recursively).
Recursive functions use O(n) space for the call stack, whereas iteration can reduce
memory to O(1).
Some recursive solutions (like regular Fibonacci) are very slow without memoization.
15
RECURSION
def fibonacci_slow(n):
if n <= 1:
return n
Recursion is a powerful tool for solving a wide range of problems, from simple calculations to
complex algorithms like tree traversal and backtracking. Below are problems categorized by
difficulty to help you practice recursion.
Code:
def sum_of_digits(n):
if n == 0:
return 0
return n % 10 + sum_of_digits(n // 10) # Sum the last digit and recurse with the rest
print(sum_of_digits(1234)) # Output: 10
Explanation:
Recursive Case: Sum the last digit (n % 10) and call the function with the rest of the
number (n // 10).
b. Reverse a String
16
RECURSION
Code:
def reverse_string(s):
if len(s) == 0:
return s
return s[-1] + reverse_string(s[:-1]) # Take the last character and recurse with the rest
Explanation:
Recursive Case: Combine the last character (s[-1]) with the reversed substring
(reverse_string(s[:-1])).
Write a recursive function to find the GCD of two numbers using Euclid's algorithm.
Code:
if b == 0:
return a
Explanation:
Recursive Case: Call the function with b and the remainder (a % b).
a. Generating Permutations
17
RECURSION
Code:
def permute(s):
if len(s) == 0:
return ['']
result = []
for i in range(len(s)):
result.append(s[i] + perm)
return result
print(permute("abc"))
Explanation:
Base Case: An empty string has only one permutation: the empty string.
Recursive Case: For each character, fix it in place and recursively find permutations of
the rest of the string.
Recursive backtracking is useful for solving puzzles like Sudoku or maze navigation.
return True
18
RECURSION
solution[x][y] = 0 # Backtrack
return False
return False
solve_maze(maze, 0, 0, solution)
print(solution)
Explanation:
Recursive Case: Try moving in all four directions (right, down, left, up), backtracking if no
valid path is found.
Write a recursive function for tree traversal. Let's use pre-order traversal as an example.
Code:
class Node:
self.value = value
self.left = None
self.right = None
def preorder_traversal(root):
if root is None:
19
RECURSION
return []
# Example Tree
root = Node(1)
root.left = Node(2)
root.right = Node(3)
root.left.left = Node(4)
Explanation:
Recursive Case: Visit the node, then recursively traverse the left and right children.
Code:
if arr[mid] == target:
return mid
else:
return -1
20
RECURSION
Explanation:
Base Case: If the search interval is invalid (low > high), return -1.
Recursive Case: Compare the middle element and recursively search the appropriate
half of the array.
Solve the N-Queens problem, where N queens must be placed on an N×N chessboard such that
no two queens threaten each other.
for i in range(row):
return False
if col - (row - i) >= 0 and board[i][col - (row - i)] == 1: # Check left diagonal
return False
if col + (row - i) < N and board[i][col + (row - i)] == 1: # Check right diagonal
return False
return True
if row == N:
return True
return True
21
RECURSION
board[row][col] = 0 # Backtrack
return False
def print_board(board):
N=4
solve_n_queens(board, 0, N)
print_board(board)
Explanation:
Recursive Case: Try placing queens in each column and backtrack if a solution is not
found.
Conclusion
💡 These practice problems range from simple (sum of digits) to advanced (N-Queens) and will
help you master recursion through hands-on experience.
Would you like help with any specific problem or a deeper dive into any technique?
1. Python
Python does not support tail call optimization. This means that recursion in Python will not
reuse the same stack frame for tail-recursive calls, leading to potential stack overflow errors if
recursion goes too deep.
22
RECURSION
Python has a default recursion depth limit of 1000 (this can be changed using
sys.setrecursionlimit()).
Example:
import sys
sys.setrecursionlimit(2000)
def factorial(n):
if n == 0:
return 1
return n * factorial(n - 1)
Backtracking and divide and conquer algorithms are commonly implemented with
recursion in Python.
2. Java
Java does not support tail call optimization either. Like Python, each recursive call adds a new
stack frame, potentially leading to stack overflow for deep recursion.
Java’s recursion depth is governed by the JVM stack size. By default, it allows a reasonable
amount of recursion, but deep recursion can lead to StackOverflowError. The stack size can be
increased with the -Xss flag when running the Java program.
Example:
23
RECURSION
if (n == 0) {
return 1;
Recursion depth depends on JVM stack size (can be adjusted with -Xss).
Recursive solutions are commonly used in search algorithms (e.g., binary search) and
tree traversal.
3. C++
C++ does support tail call optimization in some cases, depending on the compiler and compiler
flags. If the compiler detects that a function is tail-recursive (i.e., the recursive call is the last
operation), it can optimize by reusing the same stack frame instead of adding a new one.
C++ does not have a built-in recursion depth limit, but the stack size determines how deep
recursion can go before causing a stack overflow. This can be modified with compiler flags or by
setting the ulimit in Linux.
Example:
#include <iostream>
24
RECURSION
int factorial(int n) {
if (n == 0) return 1;
int main() {
return 0;
Tail call optimization depends on the compiler (e.g., GCC may optimize tail recursion).
Recursive functions are efficient in binary search and sorting algorithms (e.g., quicksort,
merge sort).
4. JavaScript
JavaScript (in ES6 and later) does support tail call optimization in some engines (e.g., V8 engine,
used in Chrome and Node.js). This allows for constant stack usage for tail-recursive functions,
making recursion more efficient.
The recursion depth limit in JavaScript depends on the JavaScript engine being used. In V8, there
is no explicit limit (other than the system's stack limit), but deep recursion can still lead to a
stack overflow error.
Example:
25
RECURSION
Feature
Python Java C++ JavaScript
Conclusion
Python and Java do not support tail call optimization, which can result in stack
overflow for deep recursions.
C++ offers tail call optimization in some cases, depending on the compiler.
JavaScript in ES6 and beyond supports tail call optimization in some engines, which
makes it more efficient for deep recursion.
Would you like a deeper look into any of these languages or their recursion-specific features?
Recursion plays a crucial role in many algorithms and data structures, enabling elegant and
efficient solutions to complex problems. Below are some key areas where recursion is widely
used.
26
RECURSION
Sorting algorithms such as Merge Sort and Quick Sort use recursion to break down large
problems into smaller subproblems.
Concept: Recursively divide the array into two halves, sort each half, and then merge
them.
Example (Python):
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
result = []
i=j=0
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
27
RECURSION
result.extend(left[i:])
result.extend(right[j:])
return result
arr = [3, 5, 1, 4, 2]
Concept: Select a pivot, partition the array around the pivot, and recursively sort the left
and right partitions.
Example (Python):
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2]
arr = [3, 5, 1, 4, 2]
Recursion simplifies searching algorithms like Binary Search, which efficiently finds an element
in a sorted array.
28
RECURSION
Concept: Compare the middle element with the target and recursively search in the left
or right half.
Example (Python):
if arr[mid] == target:
return mid
else:
arr = [1, 2, 3, 4, 5]
target = 3
Graph algorithms such as Depth-First Search (DFS) use recursion to explore nodes.
Concept: Visit a node, then recursively visit all its adjacent nodes.
Example (Python):
if visited is None:
29
RECURSION
visited = set()
visited.add(node)
graph = {
'C': ['F'],
'D': [],
'E': ['F'],
'F': []
Example (Python):
if n in memo:
30
RECURSION
return memo[n]
if n <= 1:
return n
return memo[n]
print(fibonacci(10)) # Output: 55
DFS (Depth-First
Visit nodes recursively O(V+E)O(V + E)
Search)
Conclusion
Recursive algorithms are often easier to implement but may lead to stack overflow if
not optimized properly.
To solidify the understanding of recursion, we will assess knowledge through practical problems,
comparisons between recursion and iteration, and advanced problem-solving challenges.
31
RECURSION
1. Test Understanding
✅ Sum of Digits
Write a recursive function to compute the sum of digits of a number.
def sum_of_digits(n):
if n == 0:
return 0
✅ Reverse a String
Write a recursive function to reverse a string.
def reverse_string(s):
if len(s) == 0:
return ""
✅ Greatest Common Divisor (GCD) Find the GCD of two numbers using recursion.
if b == 0:
return a
return gcd(b, a % b)
Recursion is elegant, but sometimes an iterative solution is more efficient. Let's compare both
approaches.
32
RECURSION
Recursive Approach:
def factorial_recursive(n):
if n == 0:
return 1
return n * factorial_recursive(n - 1)
Iterative Approach:
def factorial_iterative(n):
result = 1
result *= i
return result
🔹 Comparison:
When the problem has a natural recursive structure (e.g., tree traversal, DFS).
When dealing with large inputs that may cause stack overflow.
33
RECURSION
📌 Tower of Hanoi
✅ Generating Permutations
Write a recursive function to generate all permutations of a given string.
if step == len(s):
print("".join(s))
s_copy = [c for c in s]
permutations(s_copy, step + 1)
permutations(list("abc"))
✅ N-Queens Problem
Place N queens on an N × N chessboard so that no two queens attack each other.
34
RECURSION
Conclusion
Challenging problems like backtracking, DFS, and dynamic programming test deep
understanding.
🔹 Core Concepts:
🔹 Applications of Recursion:
2. Further Study
35
RECURSION
Conclusion
36