What recursion is
A function is recursive when it calls itself. Each call solves a smaller version of the same problem, and a special small case is answered directly so the chain of calls can stop. Recursion suits problems that are defined in terms of themselves, like the factorial, trees, and many divide-and-conquer algorithms.
Every recursive function has two parts:
- a base case, an input small enough to answer without calling the function again;
- a recursive case, which calls the function on a smaller input and uses the result to build the answer.
Without a base case, the calls never stop: each one starts another until the program runs out of stack space and crashes.
The exercise: factorial
The factorial of a non-negative integer n, written n!, is the product 1 * 2 * 3 * ... * n. For example, 5! = 1 * 2 * 3 * 4 * 5 = 120. By convention 0! is 1. The method factorial must compute it recursively. The statement asks for O(n) time and O(n) extra space.
The definition already contains the recursion: n! equals n times (n - 1)!. So 5! is 5 * 4!, which is 5 * 4 * 3!, and so on until 0!, which is known to be 1.
Step by step
- The base case. If
nequals 0, return 1. This is where the chain of calls ends. - The recursive case. Otherwise return
factorial(n - 1) * n. Each call hands a smaller problem to the next one.
The solution in Swift
class Solution {
func factorial(_ n: Int) -> Int {
if n == 0 {
return 1
}
return factorial(n - 1) * n
}
}
The Swift details worth a closer look:
- Calling a method of the same class. Inside
Solution, the method calls itself by plain name,factorial(n - 1). Because the parameter is declared with_, the call has no argument label. - The return type. The
-> Intis required, and every path must return anInt. Theifreturns inside its braces and the last line returns the product, so the compiler is satisfied. - Overflow traps. Swift's
Intis 64 bits.factorial(20)is 2432902008176640000 and fits, but 21! does not, and the multiplication stops the program with a run-time error instead of wrapping silently.
How the calls unfold
Calling factorial(3) produces this chain:
factorial(3) = factorial(2) * 3
factorial(2) = factorial(1) * 2
factorial(1) = factorial(0) * 1
factorial(0) = 1
The calls go down until the base case answers 1. Then each waiting call multiplies and returns: 1 * 1 = 1, then 1 * 2 = 2, then 2 * 3 = 6. Each pending call keeps its own copy of n on the call stack, so a call with argument n has n + 1 frames on the stack at its deepest point. That is the O(n) extra space in the statement, and the n + 1 calls are the O(n) time.
Edge cases and common mistakes
- Forgetting the base case. The calls never stop, and the program eventually crashes with a stack overflow.
- A base case that is never reached. The statement guarantees a non-negative
n. A negative argument would skip over 0 forever, with the same crash. - Wrong value for 0. Returning 0 for
n == 0makes every result 0. The empty product is 1. - Calling with the same argument. Writing
factorial(n)insidefactorialnever makes the problem smaller. - Large inputs. A result above 20! does not fit in an
Intand traps.
Testing
n = 0gives1.n = 1gives1.n = 5gives120.n = 20gives2432902008176640000.
Key takeaways
- A recursive function needs a base case and a recursive case that moves toward it.
- The factorial follows from n! = n * (n - 1)! with 0! = 1.
- Each pending call uses stack space, so the depth of the recursion is the extra space used.
- Swift's
Intoverflow stops the program, so factorials beyond 20! need a different type.
