FAQ
Recursion FAQ
What is a base case and why does my function need one?
A base case is the smallest input where the function returns directly without recursing. Without a base case, the function recurses forever and crashes with RecursionError in Python, StackOverflowError in Java, or a segfault in C or C++. The base case must be checked first, before the recursive call. Factorial(0) = 1; Fibonacci(0) = 0 and Fibonacci(1) = 1; sum of empty list = 0; in-order traversal of a null node returns immediately. Write the base case before the recursive case; trace on a 3-element example to verify termination.
How does the call stack work for recursive functions?
Every function call pushes a new stack frame containing local variables, parameters, return address, and saved registers. Recursive calls stack their frames sequentially: factorial(4) pushes a frame, calls factorial(3) which pushes another frame, and so on until factorial(0) returns and the frames pop in reverse order. Maximum stack depth at any instant equals the longest in-flight path. The total memory cost is depth times frame size (typically 32 to 256 bytes per frame depending on local variables). When depth exceeds the language stack limit (Python 1000, Java 10000-20000, C++ 60000-100000 on Linux), the runtime throws a stack overflow exception.
What is tail recursion and which languages optimize it?
A tail call is the last action a function takes before returning, with no further computation on the result. Tail-call optimization (TCO) reuses the current stack frame for the call instead of pushing a new one, making tail-recursive functions run in O(1) stack space. Scheme, Standard ML, Haskell, Erlang, F# perform TCO automatically per language spec. Scala does TCO when @tailrec is present. JavaScript ES6 specifies TCO in strict mode but no engine implements it. Python, Java, and most C and C++ compilers do not reliably perform TCO. For Python, Java, JavaScript: convert tail-recursive functions to iterative loops manually if deep recursion matters.
When do I need memoization?
When the recursion has overlapping subproblems (the same input is computed multiple times in the recursion tree). Naive recursive Fibonacci computes fib(2) approximately 2^(n-2) times for fib(n); memoized Fibonacci computes fib(2) once. Memoization candidates: Fibonacci, longest common subsequence, edit distance, 0/1 knapsack, matrix chain multiplication, coin change, any DP problem. Add memoization with @lru_cache(maxsize=None) in Python, ConcurrentHashMap or Caffeine in Java, std::unordered_map in C++. Bottom-up DP achieves the same complexity without recursion overhead; the choice is stylistic.
How do I fix a stack overflow in a recursive function?
Three options. First: convert to iteration with an explicit stack (std::stack in C++, java.util.Deque in Java, list in Python). Second: rewrite as tail recursion if the language supports TCO (Scheme, Haskell, Scala with @tailrec). Third: raise the language stack limit (sys.setrecursionlimit(100000) in Python, java -Xss4m in Java, ulimit -s unlimited in shell for C and C++). The first option is the most reliable and portable. The third option works for one-off scripts but does not generalize, and Python's setrecursionlimit can still segfault if the C stack underneath runs out around 30000 to 50000 frames.
What is the maximum recursion depth in Python?
CPython 3.12 default is 1000 frames (sys.getrecursionlimit returns 1000). Raise with sys.setrecursionlimit(N) up to roughly 30000 before the C stack underneath segfaults, depending on platform and per-frame variable sizes. For deeper recursion (DFS on a million-node graph, deep parsing), convert to iteration with an explicit stack. PyPy has a similar default but supports larger limits because of its stack-management strategy. The recursion limit is a global setting; raising it affects all subsequent calls in the interpreter session.
Can you help with recursive algorithms in CS61A or CS50?
Yes. CS61A uses Python and Scheme for recursion-heavy problem sets including the Hog game (recursive game-tree evaluation), the Scheme interpreter (recursive evaluation of S-expressions), and the streams lab (lazy recursive sequences). CS50 uses C and Python for recursive problem sets including the Mario pset (recursive pyramid drawing), the recover pset (recursive file recovery), and the speller pset (recursive trie traversal). Both courses grade base-case correctness, recursive-case correctness, and edge-case handling explicitly. Our tutors produce solutions with traced execution on a 3-element example, plus pytest or check50 tests covering the base case, recursive case, and 3 adversarial inputs.
How do I trace a recursive function by hand?
Draw a tree where each node represents one call. Label the node with the parameters; label edges with the return value. For factorial(4): factorial(4) -> factorial(3) -> factorial(2) -> factorial(1) -> factorial(0) returns 1; factorial(1) returns 1; factorial(2) returns 2; factorial(3) returns 6; factorial(4) returns 24. For binary recursion like fibonacci: each node has 2 children. The tree depth equals the maximum recursion depth; the tree size equals the total number of calls. Tracing by hand on a 3 or 4 element example catches most base-case and recursive-case bugs before the code is run.
When should I use recursion vs iteration?
Use recursion when the problem has self-similar structure: tree traversal, divide-and-conquer sorting, backtracking, parsing of nested expressions, graph DFS at small depths. Use iteration when stack depth is a concern (DFS on million-node graphs, deep linked-list traversal) or when the iterative version is genuinely simpler (summing an array, linear search, finding the max). Iteration is 2x to 5x faster than equivalent recursion due to per-call overhead. The default: write recursion for clarity, convert to iteration when stack depth or performance demands it.