0% found this document useful (0 votes)
5 views25 pages

Recursion Notes

This document provides comprehensive study notes on recursion for first-year students, covering its definition, anatomy, types, and examples in Python. It emphasizes the importance of base and recursive cases, the call stack, and common pitfalls, while also illustrating various recursion types and practical applications. The document includes examples such as factorial, Fibonacci sequence, and array summation to enhance understanding of recursive concepts.

Uploaded by

superstar11sanya
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views25 pages

Recursion Notes

This document provides comprehensive study notes on recursion for first-year students, covering its definition, anatomy, types, and examples in Python. It emphasizes the importance of base and recursive cases, the call stack, and common pitfalls, while also illustrating various recursion types and practical applications. The document includes examples such as factorial, Fibonacci sequence, and array summation to enhance understanding of recursive concepts.

Uploaded by

superstar11sanya
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

RECURSION

Complete Study Notes for First■Year Students

"A function that calls itself — elegantly."

■ Subject Data Structures & Algorithms

■ Level Beginner → Advanced

■ Language Python (pseudocode style)

■ Topics Base Cases · Types · Examples · Complexity

A function that solves a problem by calling itself on a smaller version


■ Self-Reference of the same problem.

The stopping condition — without it, recursion runs forever (stack


■ Base Case overflow!).

The part where the function calls itself, making progress toward the
■■ Recursive Case base case.
RECURSION — Complete Study Notes CS Fundamentals | First Year

Chapter

Table of Contents
Ch. 1 What is Recursion? Understanding the concept

Ch. 2 Anatomy of a Recursive Function Base case & Recursive case

Ch. 3 How Recursion Works Internally The Call Stack

Ch. 4 Types of Recursion 6 different types explained

Ch. 5 Basic Examples Factorial, Fibonacci, Power

Ch. 6 Intermediate Examples Arrays, Strings, Palindrome

Merge Sort, Tower of Hanoi,


Ch. 7 Advanced Examples Backtracking

Ch. 8 Recursion vs Iteration When to use which

Ch. 9 Common Mistakes & Tips Pitfalls to avoid

Ch. 10 Time & Space Complexity Analyzing recursive programs

Page 2 of 25
RECURSION — Complete Study Notes CS Fundamentals | First Year

Chapter 1

What is Recursion?
Understanding the concept from scratch

Definition
Recursion is a programming technique where a function calls itself to solve a problem by breaking it
into smaller, simpler sub-problems of the same type. Each call works on a reduced version of the
original problem until a simple, directly solvable case is reached — called the base case.

A Real-World Analogy
■ Mirror in a Mirror

Hold two mirrors facing each other. You see an infinite tunnel of reflections — each reflection
contains a smaller version of itself. That's recursion in nature.

■ Folders in Folders

A folder on your computer can contain other folders, which contain more folders. To find a file,
you open each folder, and if it has sub-folders, you repeat the same process inside them. This is
exactly recursive thinking.

■ Matryoshka Dolls

Russian nesting dolls — each doll contains a smaller doll. To count all dolls, you open one,
count it, then solve the same problem for the remaining dolls inside.

Formal Definition
A function f is called recursive if it is defined in terms of itself:

f(n) = base_case if n is simple enough

f(n) = combine(f(smaller)) otherwise

Example: f(n) = n * f(n-1) [Factorial]

f(0) = 1 [Base Case]

Page 3 of 25
RECURSION — Complete Study Notes CS Fundamentals | First Year

Why Learn Recursion?


• Many real-world problems are naturally recursive (trees, graphs, divide & conquer algorithms).
• Recursion leads to elegant, shorter code compared to iterative solutions.
• Essential for understanding sorting algorithms (Merge Sort, Quick Sort).
• Required for solving problems on binary trees, linked lists, and backtracking.
• Appears in almost every technical interview at top companies.

Page 4 of 25
RECURSION — Complete Study Notes CS Fundamentals | First Year

Chapter 2

Anatomy of a Recursive Function


Base Case & Recursive Case

The Two Essential Parts


Every correct recursive function MUST have these two parts:

Part What it does What happens without it

■ Base Case Stops the recursion when the simplest Without it → INFINITE RECURSION → Stack Overflo
case is reached

■ Recursive Case Calls the function again on a smaller/simpler


Without it → No recursion happens, just a regular fun
input

Template / Blueprint
def recursive_function(input):

# ■■ BASE CASE ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

if input is simplest possible:

return answer_directly # NO recursive call here

# ■■ RECURSIVE CASE ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

smaller_input = make_input_smaller(input)

result = recursive_function(smaller_input)

return combine(result, input)

Steps to Write Any Recursive Function


Step 1 — Identify the Base Case

Ask: 'What is the simplest input for which I know the answer directly?' For factorial, n=0 gives 1.
For sum of list, empty list gives 0.

Step 2 — Identify the Recursive Case

Page 5 of 25
RECURSION — Complete Study Notes CS Fundamentals | First Year

Ask: 'How can I express the solution for n in terms of a solution for n-1 (or smaller)?' factorial(n)
= n × factorial(n-1).

Step 3 — Trust the recursion

Assume your function works correctly for smaller inputs. You just need to use that result
correctly. This is called the 'Leap of Faith'.

Step 4 — Verify base and recursive cases connect

Make sure each recursive call brings you closer to the base case. Otherwise you'll get infinite
recursion.

Page 6 of 25
RECURSION — Complete Study Notes CS Fundamentals | First Year

Chapter 3

How Recursion Works Internally


Understanding the Call Stack

What is the Call Stack?


When a function is called, Python (and most languages) create a stack frame — a block of memory
that stores the function's local variables and the point to return to when the function finishes. The call
stack is the pile of all active stack frames, growing with each call and shrinking as calls return.

Visualizing factorial(4)
Let's trace factorial(4) step by step:

def factorial(n):

if n == 0: # base case

return 1

return n * factorial(n - 1) # recursive case

Call Stack — Going DOWN (building up):


Frame Call Waiting for...

4 (bottom) factorial(4) factorial(3) to return

3 factorial(3) factorial(2) to return

2 factorial(2) factorial(1) to return

1 factorial(1) factorial(0) to return

0 (top) factorial(0) returns 1 immediately ■

Call Stack — Going UP (unwinding):


Frame Returns Computation

0 1 base case → 1

1 1 1×1=1

2 2 2×1=2

Page 7 of 25
RECURSION — Complete Study Notes CS Fundamentals | First Year

Frame Returns Computation

3 6 3×2=6

4 24 4 × 6 = 24 ← Final Answer!

■■ Stack Overflow

If there is no base case (or it's never reached), the call stack keeps growing until memory runs
out. Python will raise: RecursionError: maximum recursion depth exceeded.

Python's default recursion limit is 1000 calls. You can check it with: import sys;
[Link]()

Page 8 of 25
RECURSION — Complete Study Notes CS Fundamentals | First Year

Chapter 4

Types of Recursion
6 Different Types with Examples

1. Direct Recursion
A function calls itself directly.

def countdown(n):

if n == 0: return

print(n)

countdown(n - 1) # direct call to itself

2. Indirect Recursion
Function A calls Function B, which calls Function A. They call each other in a cycle.

def is_even(n):

if n == 0: return True

return is_odd(n - 1) # calls is_odd

def is_odd(n):

if n == 0: return False

return is_even(n - 1) # calls is_even

3. Tail Recursion
The recursive call is the LAST operation in the function — nothing is done after it returns. Many
compilers optimize this into a loop (tail-call optimization).

def factorial_tail(n, accumulator=1):

if n == 0:

return accumulator # base case

return factorial_tail(n-1, n * accumulator) # LAST operation

Page 9 of 25
RECURSION — Complete Study Notes CS Fundamentals | First Year

4. Head Recursion
The recursive call happens FIRST, before any other processing in the function.

def print_reverse(n):

if n == 0: return

print_reverse(n - 1) # FIRST — recurse

print(n) # THEN — process (prints in order 1..n)

5. Tree Recursion
A function makes MORE THAN ONE recursive call. The calls branch out like a tree.

def fibonacci(n):

if n <= 1:

return n

return fibonacci(n-1) + fibonacci(n-2) # TWO recursive calls

6. Nested Recursion
The argument to the recursive call is itself a recursive call. Rare but interesting.

def ackermann(m, n):

if m == 0: return n + 1

if n == 0: return ackermann(m-1, 1)

return ackermann(m-1, ackermann(m, n-1)) # nested!

Page 10 of 25
RECURSION — Complete Study Notes CS Fundamentals | First Year

Chapter 5

Basic Examples
Start simple — build your intuition!

Example 1 — Factorial (n!)


Problem: Compute n! = n × (n-1) × (n-2) × ... × 1. By definition, 0! = 1.

Mathematical recurrence:
• factorial(0) = 1 [Base Case]
• factorial(n) = n × factorial(n-1) [Recursive Case]

def factorial(n):

# Base Case: 0! = 1

if n == 0:

return 1

# Recursive Case: n! = n × (n-1)!

return n * factorial(n - 1)

# Test

print(factorial(5)) # Output: 120

print(factorial(0)) # Output: 1

Call n Returns

factorial(5) 5 5 × factorial(4) = 5 × 24 = 120

factorial(4) 4 4 × factorial(3) = 4 × 6 = 24

factorial(3) 3 3 × factorial(2) = 3 × 2 = 6

factorial(2) 2 2 × factorial(1) = 2 × 1 = 2

factorial(1) 1 1 × factorial(0) = 1 × 1 = 1

factorial(0) 0 1 (Base Case!)

Example 2 — Sum of First N Numbers


Problem: Find 1 + 2 + 3 + ... + n.

Page 11 of 25
RECURSION — Complete Study Notes CS Fundamentals | First Year

def sum_n(n):

if n == 0: # Base: sum of 0 numbers = 0

return 0

return n + sum_n(n - 1) # Recursive: add n to sum of rest

print(sum_n(5)) # 5+4+3+2+1+0 = 15

Example 3 — Power (x raised to n)


Problem: Compute xn without using ** operator.

def power(x, n):

if n == 0: # x^0 = 1 always

return 1

return x * power(x, n - 1) # x^n = x * x^(n-1)

print(power(2, 10)) # Output: 1024

print(power(3, 4)) # Output: 81

■ Optimized Power — Fast Exponentiation

We can make power even faster! Instead of n calls, use n/2 calls:

If n is even: x^n = (x^(n/2))^2

If n is odd: x^n = x × x^(n-1) → This runs in O(log n) time!

def fast_power(x, n):

if n == 0: return 1

half = fast_power(x, n // 2)

if n % 2 == 0:

return half * half # Even

else:

return x * half * half # Odd

Example 4 — Print N to 1 and 1 to N


This shows the difference between head recursion and tail recursion beautifully.

Page 12 of 25
RECURSION — Complete Study Notes CS Fundamentals | First Year

# Print N down to 1 (process BEFORE recursing = tail recursion)

def print_n_to_1(n):

if n == 0: return

print(n) # Print first

print_n_to_1(n-1) # Then recurse

# Print 1 up to N (recurse BEFORE processing = head recursion)

def print_1_to_n(n):

if n == 0: return

print_1_to_n(n-1) # Recurse first

print(n) # Then print (on the way back UP)

Page 13 of 25
RECURSION — Complete Study Notes CS Fundamentals | First Year

Chapter 6

Intermediate Examples
Arrays, Strings & More

Example 5 — Fibonacci Sequence


Problem: Fibonacci: 0, 1, 1, 2, 3, 5, 8, 13 ... where fib(n) = fib(n-1) + fib(n-2).

Base cases: fib(0) = 0, fib(1) = 1

def fibonacci(n):

if n == 0: return 0 # Base case 1

if n == 1: return 1 # Base case 2

return fibonacci(n-1) + fibonacci(n-2) # Tree recursion!

for i in range(8):

print(fibonacci(i), end=' ') # 0 1 1 2 3 5 8 13

■■ Fibonacci is Slow! (Exponential Time)

fibonacci(5) calls fibonacci(3) and fibonacci(4).

fibonacci(4) ALSO calls fibonacci(3) — same work done twice!

This leads to O(2^n) time — extremely slow for large n.

Solution: Use Memoization (store results to avoid recomputation).

# Fibonacci with Memoization (much faster!)

memo = {}

def fib_memo(n):

if n <= 1: return n

if n in memo: # Already computed? Return stored value

return memo[n]

memo[n] = fib_memo(n-1) + fib_memo(n-2) # Store result

return memo[n]

Page 14 of 25
RECURSION — Complete Study Notes CS Fundamentals | First Year

print(fib_memo(50)) # Runs instantly! Without memo: takes forever

Example 6 — Sum of an Array


Problem: Find the sum of all elements in a list using recursion (no loops!).

def array_sum(arr):

# Base case: empty array has sum 0

if len(arr) == 0:

return 0

# Recursive: first element + sum of rest

return arr[0] + array_sum(arr[1:])

print(array_sum([1, 2, 3, 4, 5])) # Output: 15

print(array_sum([])) # Output: 0

Call arr Returns

array_sum([1,2,3,4,5]) [1,2,3,4,5] 1 + 14 = 15

array_sum([2,3,4,5]) [2,3,4,5] 2 + 12 = 14

array_sum([3,4,5]) [3,4,5] 3 + 9 = 12

array_sum([4,5]) [4,5] 4+5=9

array_sum([5]) [5] 5+0=5

array_sum([]) [] 0 ← Base Case

Example 7 — Check Palindrome


Problem: A palindrome reads the same forwards and backwards. Check if a string is a palindrome.

Idea: If first and last characters match, check if the middle portion is also a palindrome.

def is_palindrome(s):

# Base cases: empty string or single char = palindrome

if len(s) <= 1:

return True

# If first and last don't match → NOT palindrome

if s[0] != s[-1]:

return False

Page 15 of 25
RECURSION — Complete Study Notes CS Fundamentals | First Year

# Recurse on the middle portion

return is_palindrome(s[1:-1])

print(is_palindrome("racecar")) # True

print(is_palindrome("hello")) # False

print(is_palindrome("madam")) # True

print(is_palindrome("a")) # True

Example 8 — Reverse a String


def reverse_string(s):

if len(s) == 0: # Base: empty string

return ''

# Last character + reverse of the rest

return s[-1] + reverse_string(s[:-1])

print(reverse_string("hello")) # "olleh"

print(reverse_string("Python")) # "nohtyP"

Example 9 — Count Occurrences in Array


def count_occurrences(arr, target):

if len(arr) == 0: # Base: empty array has 0 occurrences

return 0

# Check first element, add to count of rest

count = 1 if arr[0] == target else 0

return count + count_occurrences(arr[1:], target)

print(count_occurrences([1,2,3,2,4,2], 2)) # Output: 3

Page 16 of 25
RECURSION — Complete Study Notes CS Fundamentals | First Year

Chapter 7

Advanced Examples
Tower of Hanoi · Merge Sort · Backtracking

Example 10 — Tower of Hanoi


Problem: Move n disks from peg A to peg C using peg B as helper. Rules: (1) Move one disk at a time.
(2) Never place a larger disk on a smaller one.

■ The Classic Recursion Problem

Tower of Hanoi is THE example that demonstrates recursive thinking perfectly.

The recursive insight: to move n disks from A to C, first move (n-1) disks from A to B, then move
the big disk from A to C, then move (n-1) disks from B to C.

This leads to 2^n - 1 total moves — provably optimal!

def hanoi(n, source, target, helper):

# Base Case: only 1 disk — move it directly

if n == 1:

print(f'Move disk 1 from {source} to {target}')

return

# Step 1: Move top (n-1) disks from source to helper

hanoi(n-1, source, helper, target)

# Step 2: Move the big disk from source to target

print(f'Move disk {n} from {source} to {target}')

# Step 3: Move (n-1) disks from helper to target

hanoi(n-1, helper, target, source)

hanoi(3, 'A', 'C', 'B')

Output for hanoi(3, 'A', 'C', 'B'):

Page 17 of 25
RECURSION — Complete Study Notes CS Fundamentals | First Year

Step Action

1 Move disk 1 from A to C

2 Move disk 2 from A to B

3 Move disk 1 from C to B

4 Move disk 3 from A to C

5 Move disk 1 from B to A

6 Move disk 2 from B to C

7 Move disk 1 from A to C ■ Done! (2^3 - 1 = 7 moves)

Example 11 — Merge Sort


Problem: Sort an array efficiently. Merge Sort uses divide-and-conquer recursion.

Idea: Divide the array in half, sort each half recursively, then merge the two sorted halves.

def merge_sort(arr):

# Base Case: array of 0 or 1 element is already sorted

if len(arr) <= 1:

return arr

# Divide: split into two halves

mid = len(arr) // 2

left = merge_sort(arr[:mid]) # Sort left half

right = merge_sort(arr[mid:]) # Sort right half

# Conquer: merge the two sorted halves

return merge(left, right)

def merge(left, right):

result = []

i = j = 0

while i < len(left) and j < len(right):

if left[i] <= right[j]:

[Link](left[i]); i += 1

else:

[Link](right[j]); j += 1

Page 18 of 25
RECURSION — Complete Study Notes CS Fundamentals | First Year

[Link](left[i:]) # Remaining elements

[Link](right[j:])

return result

arr = [38, 27, 43, 3, 9, 82, 10]

print(merge_sort(arr)) # [3, 9, 10, 27, 38, 43, 82]

Example 12 — Binary Search (Recursive)


Problem: Find a target in a sorted array. Binary search is naturally recursive.

def binary_search(arr, target, low, high):

# Base Case: search space is empty

if low > high:

return -1 # Not found

mid = (low + high) // 2

if arr[mid] == target:

return mid # Found!

elif arr[mid] < target:

return binary_search(arr, target, mid+1, high) # Search right

else:

return binary_search(arr, target, low, mid-1) # Search left

arr = [1, 3, 5, 7, 9, 11, 13, 15]

print(binary_search(arr, 7, 0, len(arr)-1)) # Output: 3 (index)

print(binary_search(arr, 6, 0, len(arr)-1)) # Output: -1 (not found)

Example 13 — Backtracking: Generate All Subsets


Problem: Generate all possible subsets (power set) of a given set. Backtracking is a pattern where we
explore all possibilities and undo choices that don't work.

def generate_subsets(nums, index=0, current=[]):

# Print current subset

print(current[:])

# Try including each remaining element

Page 19 of 25
RECURSION — Complete Study Notes CS Fundamentals | First Year

for i in range(index, len(nums)):

[Link](nums[i]) # Choose

generate_subsets(nums, i+1, current) # Explore

[Link]() # Un-choose (backtrack)

generate_subsets([1, 2, 3])

# Output: [] [1] [1,2] [1,2,3] [1,3] [2] [2,3] [3]

■ Backtracking Pattern

Choose: Pick an option (add to current solution).

Explore: Recurse to explore with that choice.

Un-choose: Remove the choice (backtrack) to try other options.

This pattern is used in: N-Queens, Sudoku solver, maze solving, permutations.

Page 20 of 25
RECURSION — Complete Study Notes CS Fundamentals | First Year

Chapter 8

Recursion vs Iteration
When to use which?

Aspect Recursion Iteration

Code style Shorter, elegant, mirrors math More explicit, verbose

Memory Uses call stack (extra memory) Usually constant memory

Speed Overhead from function calls Generally faster

Debugging Harder to trace Easier to trace

Best for Trees, graphs, divide & conquer Simple loops, counters

Risk Stack Overflow if no base case Infinite loop

Readability High for recursive problems High for sequential problems

Side-by-side comparison — Factorial


# Iterative

def factorial_iter(n):

result = 1

for i in range(1, n+1):

result *= i

return result

# Recursive

def factorial_rec(n):

if n == 0:

return 1

return n * factorial_rec(n-1)

Page 21 of 25
RECURSION — Complete Study Notes CS Fundamentals | First Year

Chapter 9

Common Mistakes & Tips


Avoid these pitfalls!

■ Mistake 1: Missing Base Case

This causes infinite recursion and crashes.

def bad_factorial(n):

return n * bad_factorial(n-1) # No base case! Infinite loop

■ Mistake 2: Wrong Base Case

The base case doesn't cover all stopping conditions.

def sum_n(n): # WRONG — what if n is negative?

if n == 0: return 0 # This is never reached for negative n

return n + sum_n(n-1)

def sum_n_fixed(n): # CORRECT

if n <= 0: return 0 # Handles 0 and negative

■ Mistake 3: Not Making Progress

The recursive call doesn't reduce the problem — it loops forever.

def bad(n):

if n == 0: return

bad(n) # Oops! n never changes → infinite

■ Tip 1: Trust the Recursion

When writing recursion, assume your function WORKS for smaller inputs. Focus only on the
current step.

■ Tip 2: Draw the recursion tree

Page 22 of 25
RECURSION — Complete Study Notes CS Fundamentals | First Year

For tree recursion, draw out the calls on paper. It helps you see overlapping sub-problems.

■ Tip 3: Always trace through a small example

Before coding, manually trace: factorial(3) → 3×factorial(2) → 3×2×factorial(1) → ...

Page 23 of 25
RECURSION — Complete Study Notes CS Fundamentals | First Year

Chapter 10

Time & Space Complexity


Analyzing recursive programs

For recursive programs, we use Recurrence Relations to find time complexity. Space complexity is
determined by the maximum depth of the call stack.

Algorithm Recurrence Time Space (Stack)

Factorial T(n) = T(n-1) + O(1) O(n) O(n)

Fibonacci (naive) T(n) = T(n-1) + T(n-2) O(2^n) O(n)

Fibonacci (memo) T(n) = O(n) total O(n) O(n)

Binary Search T(n) = T(n/2) + O(1) O(log n) O(log n)

Merge Sort T(n) = 2T(n/2) + O(n) O(n log n) O(n)

Power (basic) T(n) = T(n-1) + O(1) O(n) O(n)

Power (fast) T(n) = T(n/2) + O(1) O(log n) O(log n)

Tower of Hanoi T(n) = 2T(n-1) + O(1) O(2^n) O(n)

■ Master Theorem (Quick Reference)

For recurrences of the form T(n) = aT(n/b) + f(n):

• If f(n) < n^(log_b a) → T(n) = O(n^log_b_a)

• If f(n) = n^(log_b a) → T(n) = O(n^log_b_a × log n)

• If f(n) > n^(log_b a) → T(n) = O(f(n))

Example: Merge Sort: a=2, b=2, f(n)=n → f(n) = n^(log_2 2) = n → Case 2 → O(n log n)

Summary — What We Covered


Topic Key Takeaway

Page 24 of 25
RECURSION — Complete Study Notes CS Fundamentals | First Year

What is Recursion? Function that calls itself on smaller input

Base Case Stopping condition — MUST exist

Recursive Case Call with smaller input, make progress

Call Stack Each call adds a frame; unwinds when returning

Types Direct, Indirect, Tail, Head, Tree, Nested

Basic Examples Factorial, Sum, Power, Print sequences

Intermediate Fibonacci+Memo, Array Sum, Palindrome, Reverse

Advanced Tower of Hanoi, Merge Sort, Binary Search, Backtracking

vs Iteration Recursion = elegant; Iteration = efficient

Complexity Use recurrence relations; watch for stack depth

"To understand recursion, you must first understand recursion."

Page 25 of 25

You might also like