DSA Field Notes
Course map / 03 Recursion
NeetCode · DSA for Beginners · Lessons 8-9

Recursion Field Notes

A function that calls itself. One branch (factorial) and two branches (fibonacci). After this page you can trace any recursive call by hand, draw its call stack or call tree, say its Big-O, and turn it into a loop when the loop is simpler.

0Why recursion exists

Analogy

A gift box. You open it and find a smaller box inside. You open that one and find a smaller box again. You keep opening until you reach the smallest box, which holds the gift. Then you close the boxes back up from the inside out. The smallest box is the base case. Every larger box "contains a smaller version of the same problem": that is the recursive case.

Recursion is when a function calls itself, usually with a different (smaller) input. A function that does this is a recursive function. It breaks a problem into smaller sub-problems of the same shape, and then solves them in reverse order: the smallest one is finished first, and the biggest one is finished last.

Every recursive function has exactly two parts:

  • Base case: the input is so small that you answer directly, with no further call. Without it the function never stops.
  • Recursive case: the function calls itself on a smaller input and uses that result to build its own answer.

There are two shapes. One-branch recursion makes one recursive call per step (factorial, lesson 8). Multi-branch recursion makes two or more (fibonacci, lesson 9), which draws a tree of calls. It is usually possible to convert recursion to iteration and back. Sometimes the loop is simpler, sometimes the recursion is. Trees and graphs, which come later in the course, are where recursion is the natural choice.

factorial(5) factorial(4) factorial(3) factorial(2) factorial(1) = 1 each box holds one smaller box (one branch) OPEN = recursive case factorial(n) cannot answer alone; it needs the box inside: factorial(n - 1) STOP = base case the smallest box has nothing inside; factorial(1) answers 1 with no new call CLOSE = unwind answers travel back out, inside to outside
Open, stop, close. Opening boxes is the "going down" half of recursion. Reaching the orange box is the base case. Closing boxes is the "coming back up" half, where the sub-answers are pieced together. Both halves matter; beginners only picture the first one.

1One branch: factorial

n! as a smaller copy of itself

n factorial, written n!, is the product of all whole numbers from n down to 1: n! = n × (n-1) × (n-2) × ... × 1. Example: 5! = 5 × 4 × 3 × 2 × 1 = 120.

Look at the same product again: 5! = 5 × 4!, and 4! = 4 × 3!, and so on. Each factorial is "n times a smaller factorial". That is the recursive case. The chain has to end somewhere: 1! = 1 and, by definition, 0! = 1. That is the base case.

# Recursive implementation of n! (n-factorial) calculation
def factorial(n):
    # Base case: n = 0 or 1
    if n <= 1:
        return 1
    # Recursive case: n! = n * (n - 1)!
    return n * factorial(n - 1)

Two things to notice. The base case comes first, so it is checked before any new call. And the recursive call is on a smaller input (n - 1), so every call moves toward the base case.

Trace factorial(5): down, then up

factorial(5) wants to return 5 * factorial(4). It cannot finish until factorial(4) answers. So it waits, and factorial(4) calls factorial(3), which calls factorial(2), which calls factorial(1). That last one is the base case and returns 1 without calling anyone. Now the waiting calls finish in reverse: 2 × 1 = 2 (that is 2!), 3 × 2 = 6, 4 × 6 = 24, 5 × 24 = 120.

GOING DOWN: each call waits factorial(5) 5 × 24 = 120 factorial(4) 4 × 6 = 24 factorial(3) 3 × 2 = 6 factorial(2) 2 × 1 = 2 factorial(1) return 1 ① calls factorial(4) ② calls factorial(3) ③ calls factorial(2) ④ calls factorial(1) ⑥ returns 1 ⑦ returns 2 (= 2!) ⑧ returns 6 (= 3!) ⑨ returns 24 (= 4!) ⑩ returns 120 = the answer ⑤ base case: n ≤ 1, return 1, no further call
Nothing is multiplied on the way down. Steps ① to ④ only make calls. All the arithmetic (orange) happens on the way up, ⑥ to ⑩, after the base case ⑤. The answer for the biggest call is the last thing computed.
StepCalln <= 1?DoesWaits forReturns
①factorial(5)no5 * factorial(4)factorial(4)(later) 5 × 24 = 120
②factorial(4)no4 * factorial(3)factorial(3)(later) 4 × 6 = 24
③factorial(3)no3 * factorial(2)factorial(2)(later) 3 × 2 = 6
④factorial(2)no2 * factorial(1)factorial(1)(later) 2 × 1 = 2
⑤factorial(1)yesreturn 1nobody1, immediately

Read the table top to bottom for the calls, then bottom to top for the returns. Step ⑤ returns first; step ① returns last. That is what "solve the sub-problems in reverse order" means.

See it happen: a traced version

Add a depth argument and print with indentation. This is the single best way to build intuition; do it once for every recursive function you write this week.

def factorial_traced(n, depth=0):
    pad = "    " * depth
    print(f"{pad}factorial({n}) called")
    if n <= 1:
        print(f"{pad}base case -> return 1")
        return 1
    sub = factorial_traced(n - 1, depth + 1)   # wait here until the smaller call returns
    print(f"{pad}factorial({n}) = {n} * {sub} = {n * sub}")
    return n * sub

factorial_traced(5)
# factorial(5) called
#     factorial(4) called
#         factorial(3) called
#             factorial(2) called
#                 factorial(1) called
#                 base case -> return 1
#             factorial(2) = 2 * 1 = 2
#         factorial(3) = 3 * 2 = 6
#     factorial(4) = 4 * 6 = 24
# factorial(5) = 5 * 24 = 120
Keep in mind

The line sub = factorial_traced(n - 1, ...) is where the function pauses. Everything after that line runs only when the smaller call has fully finished. Five copies of the function are alive at the same time, each paused at that line with its own n.

No base case = no end

If you delete if n <= 1: return 1, factorial(5) calls factorial(4), ... factorial(0), factorial(-1), and so on forever. Each call uses a little memory, so the program crashes with a stack overflow: the call stack ran out of room. In Python the crash is RecursionError: maximum recursion depth exceeded. Section 2 explains the stack.

2The call stack: where the space goes

Analogy

A pile of sticky notes. Each time a function is called you write its variables on a note and put it on top of the pile. When that function returns, you throw the top note away and continue with the note under it. The pile is the call stack. Its height is the recursion depth.

factorial creates no list, no dictionary, nothing. So why is its space O(n)? Because recursion uses an implicit stack: the function call stack that the language keeps for you. Each call gets a stack frame holding its own n and the place to return to. That is how "return from this call to the previous one" works at all. factorial(5) has five frames alive at once, so the stack holds n frames: O(n) space even though you wrote no data structure.

f(5) f(5) f(4) f(5) f(4) f(3) f(5) f(4) f(3) f(2) f(5) f(4) f(3) f(2) f(1) peak: 5 frames f(5) f(4) f(3) f(2) f(1) ↑ 1 f(5) f(4) f(3) f(2) ↑ 2 f(5) f(4) f(3) ↑ 6 f(5) f(4) ↑ 24 f(5) ↑ 120 t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 grows: push one frame per call shrinks: pop one frame per return
The stack is tallest exactly when the base case runs. Orange outline = the frame pushed in that step; faded = the frame popped, with the value it hands down (orange). The top frame is always the one running; the frames under it are paused at their recursive call.
factorial(n)CostWhy
TimeO(n)n calls (n, n-1, ..., 1), each does one comparison and one multiplication: O(1) work per call
SpaceO(n)n frames on the call stack at the peak; no explicit data structure, but the implicit stack still counts

Stack overflow and Python's recursion limit

The call stack has a fixed size. Go too deep and you get a stack overflow. Python guards this with a depth limit, 1000 frames by default, and raises RecursionError when you cross it. You can raise the limit for a deep but correct recursion (a linked list of 5000 nodes, for example). It does not fix a missing base case; it only delays the crash.

import sys
sys.getrecursionlimit()          # 1000 by default in CPython
sys.setrecursionlimit(10_000)    # allow deeper recursion (use with care)

factorial(3000)                  # ok now; RecursionError under the default limit

def no_base_case(n):
    return n * no_base_case(n - 1)   # 5, 4, 3, ..., -900, ... never stops

no_base_case(5)   # RecursionError: maximum recursion depth exceeded
Interview note

If an interviewer asks "what if n is a million?", the honest answer is: recursion would blow the stack, so I would convert to a loop (next heading) or use an explicit stack. Saying sys.setrecursionlimit alone is not a real fix at that size.

Iteration vs recursion

The same product with a loop. NeetCode's version stores the running answer in res and decrements n until it reaches 1:

n = 5
res = 1
while n > 1:
    res = res * n
    n -= 1
# res is 120, n is 1

# the same thing as a function
def factorial_iter(n):
    res = 1
    while n > 1:
        res = res * n
        n -= 1
    return res
Recursive factorialIterative factorial_iter
TimeO(n)O(n)
SpaceO(n) call stackO(1) two variables
Where the answer livesspread across n paused framesin res
Reads best whenthe problem is a tree (later lessons)the problem is a straight line, like this one

For factorial the loop is simpler and cheaper. That is not always true. For trees, where every node has two children, the recursive version is usually a few lines and the loop needs you to manage your own stack. Rule: iterative is more space-efficient (no call stack); recursive is often shorter and clearer for branching problems.

3Two branches: fibonacci

Multi-branch recursion, where a function calls itself more than once, is more common in practice than one-branch. The classic example is the Fibonacci sequence. It starts 0, 1, and each next number is the sum of the two before it:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...

Base cases: F(0) = 0 and F(1) = 1. Recursive case: F(n) = F(n-1) + F(n-2). A formula that defines a value in terms of smaller values of itself is called a recurrence relation: fib(n) = fib(n-1) + fib(n-2). You will meet recurrence relations again in dynamic programming.

# Recursive implementation to calculate the n-th Fibonacci number
def fibonacci(n):
    # Base case: n = 0 or 1
    if n <= 1:
        return n
    # Recursive case: fib(n) = fib(n - 1) + fib(n - 2)
    return fibonacci(n - 1) + fibonacci(n - 2)

Note the base case returns n itself, not 1: fibonacci(0) is 0 and fibonacci(1) is 1. The last line calls the function twice. That is a branch factor of two: every call spawns two more, so the calls form a tree instead of a chain.

Trace fibonacci(5): a tree, not a chain

fibonacci(5) = fibonacci(4) + fibonacci(3). Python evaluates the left call first, so fibonacci(4) runs to completion (it calls fibonacci(3) + fibonacci(2), and so on down to 1 or 0) before fibonacci(3) on the right even starts. Then the values come back up: fibonacci(4) = 3, fibonacci(3) = 2, so fibonacci(5) = 5.

F(3): the 1st time F(3): the 2nd time, same work F(5) =5 F(4) =3 F(3) =2 F(3) =2 F(2) =1 F(2) =1 F(1) =1 F(2) =1 F(1) =1 F(1) =1 F(0) =0 F(1) =1 F(0) =0 F(1) =1 F(0) =0 orange ring = F(2) computed 3 times · 15 calls in total for n = 5
The same sub-problem is solved again and again. F(3) is computed twice (dashed regions) and F(2) three times (orange rings), each time from scratch. That repeated work is why plain recursive fibonacci is slow, and it is exactly what memoization and dynamic programming remove (section 4, move 4). Values in orange are what each call returns; left children finish before right children.

Trace table: fibonacci(4), every call and every return

The tree hides the order. This table shows the real order of events for fibonacci(4): 9 calls, 9 returns, and the stack after each event (bottom on the left, top on the right).

#EventCall stack after itValue
1call F(4)[F4]needs F(3) + F(2)
2call F(3) (left)[F4, F3]needs F(2) + F(1)
3call F(2) (left)[F4, F3, F2]needs F(1) + F(0)
4call F(1) (left)[F4, F3, F2, F1]base case
5return from F(1)[F4, F3, F2]1
6call F(0) (right)[F4, F3, F2, F0]base case
7return from F(0)[F4, F3, F2]0
8return from F(2)[F4, F3]1 + 0 = 1
9call F(1) (right)[F4, F3, F1]base case
10return from F(1)[F4, F3]1
11return from F(3)[F4]1 + 1 = 2
12call F(2) (right)[F4, F2]needs F(1) + F(0), again
13call F(1) (left)[F4, F2, F1]base case
14return from F(1)[F4, F2]1
15call F(0) (right)[F4, F2, F0]base case
16return from F(0)[F4, F2]0
17return from F(2)[F4]1 + 0 = 1
18return from F(4)[]2 + 1 = 3

Two facts fall out of the table. The stack never holds more than 4 frames (n frames in general), so space is O(n). And F(2) was fully recomputed at step 12 even though step 8 already knew the answer.

Count the calls

A quick experiment before the theory: add a counter.

calls = 0

def fib_counted(n):
    global calls
    calls += 1
    if n <= 1:
        return n
    return fib_counted(n - 1) + fib_counted(n - 2)

# n:      4    5    10     20
# calls:  9   15   177  21891     roughly doubles every time n grows by 1

Time complexity: count the tree level by level

Look at the tree by levels (rows). Level 0 has 1 node, the root. Each node makes two calls, so level 1 can hold 2 nodes, level 2 can hold 4, level 3 can hold 8. Each level can hold twice the previous one. The deepest level is n (the path n, n-1, n-2, ..., 0 down the left side). So the total number of nodes is at most:

1 + 2 + 4 + 8 + ... + 2^n

This is a geometric series, the same kind you met when a dynamic array doubles its capacity. The last term dominates: the sum is 2^(n+1) - 1, which is less than 2 × 2^n. Drop the constant and the total number of nodes is O(2^n). Each node is one function call that does one addition of two values, O(1) work. So the time complexity is O(2^n).

level 0: 1 node = 2^0 level 1: 2 nodes = 2^1 level 2: 4 nodes = 2^2 level 3: 8 nodes = 2^3 ... level n: 2^n nodes 1 + 2 + 4 + 8 + ... + 2^n = 2^(n+1) - 1 → O(2^n) calls
Each level doubles, and the last level is bigger than all the others combined. That is why the sum of a geometric series is "about twice the last term", and why the last term alone sets the Big-O. The real fibonacci tree is thinner than this (the n-2 branch is shorter), so 2^n is an upper bound; the true growth is about 1.618^n, still exponential.
fibonacci(n), plain recursionCostWhy
TimeO(2^n)up to 2^(n+1) - 1 calls, O(1) work each
SpaceO(n)the call stack is at most n deep at any moment (one root-to-leaf path), even though the tree has 2^n nodes
Keep in mind

Space is about the height of the tree, not its size. Only one root-to-leaf path is on the stack at a time; finished branches have been popped. NeetCode's article states the O(2^n) time; the O(n) space is the standard fact that goes with it.

4Four moves that make recursion routine

The lessons teach factorial and fibonacci. Interviews test whether you can set up any recursion cleanly, and whether you know when to leave it. These four moves cover that.

Move 1: Base case first (the checklist)

Before typing the recursive call, answer three questions in this order. Write the base case first, physically at the top of the function.

  1. What is the smallest input? Empty list, n = 0, None node, l >= r. Return its answer directly. This is the base case.
  2. How do I make the input smaller by one step? n - 1, i + 1, node.next, n // 2. Call yourself on that. Every call must move toward the base case.
  3. Given the smaller answer, how do I build mine? n * sub, nums[i] + sub, left + right. Trust that the recursive call returns the right thing. Do not trace it in your head; that is the whole point.
def sum_list(nums, i=0):
    if i == len(nums):              # 1. base case FIRST: nothing left to add
        return 0
    return nums[i] + sum_list(nums, i + 1)   # 2. shrink   3. combine

sum_list([2, 3, 4, 1, 6])   # 16
sum_list([])                # 0  (the base case handles empty input for free)
Keep in mind

Pass an index (i + 1), not a slice (nums[1:]). A slice copies the rest of the list on every call and turns O(n) into O(n²).

Move 2: Recursion to iteration

When the interviewer worries about stack depth, or when the recursion is one-branch and the answer is built on the way up, convert to a loop. Two recipes:

  • One branch, combine on the way up (factorial, sum): keep the running answer in a variable and loop toward the base case. You saw this in section 2.
  • Two branches (fibonacci): go bottom-up. Start from the base cases and build forward, keeping only the last two values. This is the seed of dynamic programming.
  • Anything else: replace the call stack with your own list used as a stack. The trees lesson uses this for iterative traversals.
def fib_iter(n):
    a, b = 0, 1                     # a = fib(i), b = fib(i + 1), starting at i = 0
    for _ in range(n):
        a, b = b, a + b             # slide the window one step forward
    return a                        # O(n) time, O(1) space, no stack

def sum_list_iter(nums):
    stack = [0]                     # our own stack replaces the call stack
    total = 0
    while stack:
        i = stack.pop()
        if i == len(nums):           # the old base case
            continue
        total += nums[i]
        stack.append(i + 1)         # the old recursive call
    return total

Move 3: Helper function with an accumulator

Sometimes the public function signature is fixed (LeetCode gives you reverseString(s)) but your recursion needs extra state: an index, two pointers, a running total. Write an inner helper that takes the extra arguments, and call it once. An accumulator is an argument that carries the answer-so-far down the calls, so nothing is left to do on the way up.

def factorial_acc(n):
    def helper(n, acc):
        if n <= 1:
            return acc               # the answer is already finished
        return helper(n - 1, acc * n)  # multiply BEFORE the call, not after
    return helper(n, 1)

def reverseString(s):                # LeetCode 344: reverse list s in place
    def helper(l, r):
        if l >= r:                   # pointers met or crossed: done
            return
        s[l], s[r] = s[r], s[l]
        helper(l + 1, r - 1)
    helper(0, len(s) - 1)
helper(5, 1) helper(4, 5) helper(3, 20) helper(2, 60) helper(1, 120) 1×5 5×4 20×3 60×2 n ≤ 1 → return 120 acc travels DOWN; on the way UP every frame passes 120 along unchanged
Compare with the first factorial diagram. There, the multiplications (orange) happened on the way up. Here they happen on the way down, and the base case already holds the final answer. Both are O(n) time and O(n) stack in Python, but the accumulator version is the one that converts to a loop almost mechanically: acc becomes res.

Move 4: Memoization, a one-line preview

The fibonacci tree computed F(2) three times. Memoization means "remember each answer the first time, and look it up after that". In Python it is one decorator, and it turns O(2^n) into O(n) because each distinct n is computed once. The full treatment, including the bottom-up table version, is on the Dynamic Programming page.

from functools import lru_cache

@lru_cache(maxsize=None)          # remembers every (n -> result) pair
def fib_memo(n):
    if n <= 1:
        return n
    return fib_memo(n - 1) + fib_memo(n - 2)

fib_memo(50)    # 12586269025, instantly. Plain recursion would take ~20 billion calls.
F(5) =5 F(4) =3 F(3) =2 F(2) =1 F(1) =1 F(0) =0 F(1) hit F(2) hit F(3) hit F(1) F(0) F(2) F(1) F(1) F(0) faded = never runs; the answer came from the cache 6 computations + 3 cache hits = 9 calls instead of 15; n = 50: 99 calls instead of ~20 billion
With a cache, the tree collapses to its left spine. Every n from 0 to 5 is computed once (plain nodes), and the three repeated calls (orange rings) are answered from memory without opening their subtrees. Calls become O(n) and the stack is still O(n).

5Big-O summary

Say the time and the space separately, and say where the space comes from ("the call stack") even when there is no data structure.

FunctionShapeTimeSpaceWhy
factorial(n) recursiveone branchO(n)O(n)n calls, O(1) each; n frames on the stack at the peak
factorial_iter(n)loopO(n)O(1)same n steps, two variables, no stack
fibonacci(n) plain recursivetwo branchesO(2^n)O(n)up to 2^(n+1) - 1 calls; stack depth is the tree height, n
fib_memo(n) with lru_cachetwo branches + cacheO(n)O(n)each n computed once; cache and stack both hold n entries
fib_iter(n) bottom-uploopO(n)O(1)keeps only the last two values
General rule: one branch, depth dO(d)O(d)a chain of d calls
General rule: b branches, depth dO(b^d)O(d)a tree with b^d leaves; only one path is on the stack at a time

The last two rows are the ones to memorize. Time counts every node in the call tree. Space counts the height of that tree, because that is the most frames that are ever alive together.

6Mistakes to watch for

Missing or unreachable base case

No if n <= 1: return, or a base case that the input can skip past (checking n == 0 when n can start negative). Result: RecursionError. Write the base case first, and make sure every recursive call moves toward it.

Forgetting to return the recursive call

factorial(n - 1) on its own line does the work and throws the result away; the function returns None. Write return n * factorial(n - 1). Same for helpers: if the helper computes the answer, return it from the outer function too.

Wrong base value

factorial returns 1 at the base case (the empty product). fibonacci returns n (0 for 0, 1 for 1). sum returns 0. Returning the wrong "nothing" value shifts every answer.

Tracing the whole tree in your head

You will get lost by depth 3. Trust the recursive call: assume fibonacci(n - 1) is correct and only check that the base case and the combine step are right. Trace on paper only when debugging.

Saying "O(1) space" for recursion

There is no list, but there is a call stack. One-branch recursion of depth n is O(n) space. Interviewers listen for this.

Slicing instead of indexing

helper(nums[1:]) copies the rest of the list on every call: O(n²) time and memory. Pass an index i + 1 and keep one list.

Using plain recursion on fibonacci-shaped problems

If the same sub-problem appears twice in the tree (climbing stairs, fibonacci, grid paths), plain recursion is exponential. Add @lru_cache or a memo dictionary, or go bottom-up.

Mutable default argument as a memo

def fib(n, memo={}) works but the dictionary is shared across every call of the function forever, including different test cases. Prefer @lru_cache, or create the dictionary in the outer function and pass it to a helper.

Deep recursion on big linear inputs

A recursive walk over a 100,000-node linked list crosses Python's 1000-frame limit. For long chains use a loop; keep recursion for trees, where depth is usually log n.

Interview one-liners

  • "A recursive function has a base case that answers directly and a recursive case that calls itself on a smaller input."
  • "The sub-problems are solved in reverse order: the base case finishes first, the original call finishes last."
  • "Recursion is not free in space: each call adds a frame to the call stack, so depth n means O(n) space."
  • "One-branch recursion of depth n is O(n) time. Two-branch recursion of depth n is O(2^n) time because the call tree has about 2^n nodes; the last level dominates the geometric series."
  • "Space is the height of the call tree, not its size; only one root-to-leaf path is on the stack at a time."
  • "Plain recursive fibonacci recomputes the same sub-problems; memoization makes each n cost once, so O(n)."
  • "Any recursion can be made iterative with an explicit stack; for one-branch cases a loop with an accumulator is enough."

7Practice, in this order

For each one, write the base case first, say the time and space out loud, then code it. Several of these have a loop solution; do the recursive version anyway, that is what this page trains.

#ProblemMove it trainsLevel
509Fibonacci Numbertwo-branch recursion, then add @lru_cache, then bottom-up loopEasy
1137N-th Tribonacci Numberthree-branch recurrence; memo or loop with three variablesEasy
70Climbing Stairsfibonacci in disguise; recognize the recurrence f(n) = f(n-1) + f(n-2)Easy
344Reverse String (recursive version)helper with two pointers l, r; base case l >= rEasy
231Power of Twoone-branch on n // 2; base cases n == 1 and odd nEasy
206Reverse Linked List (recursive version)trust the call: reverse the rest, then fix two pointers on the way upEasy
21Merge Two Sorted Lists (recursive version)base case: one list empty; pick the smaller head, recurse on the restEasy
404Sum of Left Leavestwo-branch recursion on a tree; combine left + rightEasy
24Swap Nodes in Pairs (recursive version)one-branch on head.next.next; rewire on the way upMedium
50Pow(x, n)halve the input: power(x, n // 2) squared, O(log n) depth; handle negative nMedium

The recursive reverse for 206, since it is the one people find hardest to picture:

def reverseList(head):
    if head is None or head.next is None:   # base case: empty or single node
        return head
    new_head = reverseList(head.next)       # trust: reverses everything after head
    head.next.next = head                   # the old "next" now points back at head
    head.next = None                        # head becomes the tail
    return new_head                         # same new head is passed all the way up

Your next step today: LeetCode 509. Write the plain recursive version, count the calls for n = 30 with a counter, then add @lru_cache and count again.

8Self-check

Answer out loud before opening. If you cannot answer in one breath, reread that section.

Q1What are the two parts of every recursive function, and what happens if one is missing?

The base case (answer directly, no call) and the recursive case (call yourself on a smaller input). Without a base case the calls never stop and the program crashes with a stack overflow; in Python, RecursionError.

Q2Why does the lesson say recursion solves the sub-problems "in reverse order"?

Because the biggest call starts first but finishes last. factorial(5) is the first call made and the last to return; factorial(1) is the last call made and the first to return. All the multiplication happens on the way back up.

Q3Trace it: factorial(4). List the calls in the order they are made, then the returns in the order they happen.

Calls: factorial(4), factorial(3), factorial(2), factorial(1). Returns: factorial(1) → 1, factorial(2) → 2 × 1 = 2, factorial(3) → 3 × 2 = 6, factorial(4) → 4 × 6 = 24.

Q4factorial creates no list or dictionary. Why is its space complexity O(n)?

Each call adds a frame to the call stack (the implicit stack the language keeps), and all n frames are alive at once when the base case runs. n frames → O(n) space.

Q5What is the time and space of the iterative factorial, and why is it better here?

O(n) time, O(1) space: a loop with two variables and no call stack. For a straight-line problem like factorial the loop is both simpler and cheaper. Recursion earns its keep on branching problems like trees.

Q6What does Python do at 1000 nested calls, and what can you change?

It raises RecursionError: maximum recursion depth exceeded. sys.setrecursionlimit(k) raises the limit. It helps a deep but correct recursion; it does not fix a missing base case, and for very large inputs a loop or explicit stack is the real fix.

Q7Write the recurrence relation for Fibonacci, including both base cases.

F(0) = 0, F(1) = 1, and F(n) = F(n-1) + F(n-2) for n ≥ 2. In code the base case is if n <= 1: return n.

Q8Trace it: fibonacci(4). How many calls are made in total, what is the deepest the stack gets, and which sub-problem is computed twice?

9 calls. The stack reaches 4 frames (F4, F3, F2, F1). F(2) is computed twice: once under F(3) and once directly under F(4). F(1) is also called three times. Result: 3.

Q9Explain, level by level, why two-branch recursion of depth n is O(2^n).

Level 0 has 1 node, level 1 has 2, level 2 has 4: each level can hold twice the previous, up to 2^n at level n. The total is the geometric series 1 + 2 + 4 + ... + 2^n = 2^(n+1) - 1. The last term dominates, so the node count is O(2^n), and each node is O(1) work.

Q10The fibonacci call tree has about 2^n nodes. Why is the space only O(n)?

Space is the maximum number of frames alive at one moment, which is one root-to-leaf path, of length at most n. Finished branches have already been popped off the stack.

Q11In one sentence, what does memoization change about the fibonacci tree?

Each distinct n is computed once and then served from a cache, so the repeated subtrees never run; calls drop from O(2^n) to O(n). Details on the DP page.

Q12What is an accumulator, and what is different about where the work happens?

An extra argument that carries the answer-so-far down into each call, like helper(n - 1, acc * n). The work happens on the way down, and the base case already holds the final answer, so the way up just passes it along. That shape converts to a loop directly.