Why Recursion Exists and What Loops Cannot Express Cleanly in DSA Rust - Performance Analysis
We want to understand why recursion is useful and how its time cost grows compared to loops.
What question are we trying to answer? How does recursion's repeated calls affect performance?
Analyze the time complexity of the following recursive function.
fn factorial(n: u32) -> u32 {
if n == 0 {
1
} else {
n * factorial(n - 1)
}
}
This function calculates the factorial of a number using recursion.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Recursive call to factorial with n-1.
- How many times: Exactly n + 1 times until base case n=0 is reached.
Each call waits for the next call to finish, so the total steps grow directly with n.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 multiplications and calls |
| 100 | About 100 multiplications and calls |
| 1000 | About 1000 multiplications and calls |
Pattern observation: The number of operations grows linearly as n increases.
Time Complexity: O(n)
This means the time to complete grows in direct proportion to the input size.
[X] Wrong: "Recursion always makes code slower than loops because it repeats work unnecessarily."
[OK] Correct: Recursion here does not repeat work; each call does unique work. Loops and recursion can have the same time cost if they do the same steps.
Understanding recursion's time cost helps you explain why it's chosen for some problems and how to reason about its efficiency confidently.
"What if we changed the recursion to a loop that multiplies from 1 to n? How would the time complexity change?"
Practice
fn factorial(n: u32) -> u32 { if n == 0 { 1 } else { n * factorial(n - 1) } }Solution
Step 1: Understand recursion's purpose
Recursion is useful when a problem can be broken down into smaller similar problems, like factorial.Step 2: Compare recursion and loops
Loops repeat actions but do not naturally express self-similar or nested problems as clearly as recursion.Final Answer:
Recursion can express problems with self-similar structure more naturally. -> Option CQuick Check:
Recursion = natural for self-similar problems [OK]
- Thinking loops are always slower
- Believing recursion uses less memory
- Assuming loops cannot repeat actions
Solution
Step 1: Identify correct base case and recursion
fn sum(n: u32) -> u32 { if n == 0 { 0 } else { n + sum(n - 1) } } correctly returns 0 when n is 0 and sums recursively.Step 2: Check syntax and logic
Options B and C misuse loops inside recursion, causing infinite loops or errors. fn sum(n: u32) -> u32 { if n == 0 { 1 } else { n * sum(n - 1) } } multiplies instead of sums.Final Answer:
fn sum(n: u32) -> u32 { if n == 0 { 0 } else { n + sum(n - 1) } } -> Option AQuick Check:
Correct recursion syntax = fn sum(n: u32) -> u32 { if n == 0 { 0 } else { n + sum(n - 1) } } [OK]
- Using loops inside recursive function incorrectly
- Wrong base case value
- Mixing sum and product operations
fn print_numbers(n: u32) {
if n == 0 {
return;
}
print_numbers(n - 1);
println!("{}", n);
}
fn main() {
print_numbers(3);
}Solution
Step 1: Trace recursive calls
print_numbers(3) calls print_numbers(2), then print_numbers(1), then print_numbers(0) which returns immediately.Step 2: Print after recursion unwinds
After reaching 0, the calls print 1, then 2, then 3 in order.Final Answer:
1 2 3 -> Option AQuick Check:
Recursion prints ascending after calls [OK]
- Assuming print happens before recursion
- Confusing order of prints
- Ignoring base case return
fn fib(n: u32) -> u32 {
if n == 0 {
0
} else if n == 1 {
1
} else {
fib(n - 1) + fib(n - 2);
}
}Solution
Step 1: Check function branches for return values
The else branch ends with an expression but lacks a return or semicolon, causing no value returned.Step 2: Understand Rust expression rules
In Rust, the last expression without semicolon is returned. Here, semicolon after fib(n - 1) + fib(n - 2) prevents return.Final Answer:
Missing return statement in the else branch causes no value returned. -> Option DQuick Check:
Semicolon stops return in else branch [OK]
- Ignoring semicolon effect on return
- Thinking base cases are wrong
- Confusing recursion with loops
fn flatten(list: &[Nested]) -> Vec<i32> {
// Nested is enum: Int(i32) or List(Vec<Nested>)
// Fill this function
}Solution
Step 1: Understand nested list structure
Nested lists can contain integers or other lists, requiring a method to explore all levels.Step 2: Apply recursion to handle arbitrary depth
Recursion naturally handles nested structures by calling itself on sublists, collecting all integers.Final Answer:
Use recursion to check each element: if Int, add to result; if List, recursively flatten and extend result. -> Option BQuick Check:
Recursion handles nested depth cleanly [OK]
- Trying to flatten nested lists with simple loops
- Ignoring nested depth in loops
- Using loops that skip nested elements
Ready to prove it?
Pass the test and this pattern moves unknown
