10.3 Functions & Recursion
Key Takeaways
- A function packages parameters, a body, and an optional return value; calling it transfers control, then resumes after the call with the returned result
- The call stack grows with each nested call and shrinks on return — each frame holds its own local variables and return address
- Recursion means a function calls itself; every correct recursive design needs a base case that stops and a recursive case that moves toward that base
- Missing or unreachable base cases cause infinite recursion / stack overflow; wrong base cases yield wrong answers even if the stack stays finite
- Tracing recursion means expanding calls until the base case, then substituting returned values on the way back up the stack
Functions let you name a reusable chunk of logic. Recursion is the special case where that chunk calls itself. Cyber Test items on this topic are almost always trace-the-calls problems: what does f(3) return, how many times does a body run, or what goes wrong when the base case is missing?
Functions: Inputs, Body, Output
A function (also called a procedure or method in related contexts) has:
| Piece | Role |
|---|---|
| Name | How you call it |
| Parameters | Placeholders for incoming values |
| Body | Statements that run on each call |
| Return value | Result handed back to the caller (optional for "void" actions) |
Language-agnostic:
FUNCTION add(a, b)
RETURN a + b
END FUNCTION
SET s TO add(2, 5) # s becomes 7
Python:
def add(a, b):
return a + b
s = add(2, 5)
C++:
int add(int a, int b) {
return a + b;
}
int s = add(2, 5);
Parameter Binding
On a call add(2, 5), parameter a gets 2 and b gets 5 for that invocation. Those names are local: they do not permanently overwrite same-named variables in the caller unless the language uses explicit reference/pointer semantics (advanced for this exam). For aptitude items, assume pass-by-value: the function works on copies; assigning to a parameter inside does not change the caller's variable.
Return Ends the Function
When return executes, the function stops. Statements after an executed return in the same path do not run. Multiple return points (often inside if branches) are normal; the first one hit wins for that call.
Call Stack Intuition
Think of the call stack as a stack of plates. Each function call pushes a frame (locals, parameters, where to resume). Each return pops a frame.
FUNCTION main()
SET x TO double(3)
RETURN x
END
FUNCTION double(n)
RETURN n * 2
END
mainstarts → frame:maindouble(3)called → stack:main|double(n=3)doublereturns 6 → popdouble;mainresumes withx = 6mainreturns → stack empty
You do not need OS-level detail. You need: deeper call = more frames, return = resume caller with a value.
Nested Calls
SET r TO add(double(2), 4)
Order: evaluate double(2) first (returns 4), then add(4, 4) (returns 8). Inner calls complete before the outer call that needs their results.
Recursion: Functions That Call Themselves
Recursion solves a problem by reducing it to a smaller instance of the same problem until a trivial case appears.
Every correct recursive function needs:
- Base case — condition where you return a known answer without calling yourself again
- Recursive case — call yourself on a smaller input, then combine the result
Classic: Factorial
Definition: n! = 1 when n is 0 or 1; otherwise n! = n * (n-1)!.
FUNCTION fac(n)
IF n <= 1 THEN
RETURN 1
ELSE
RETURN n * fac(n - 1)
END IF
END FUNCTION
Trace fac(4) (expand downward):
fac(4) → 4 * fac(3)
fac(3) → 3 * fac(2)
fac(2) → 2 * fac(1)
fac(1) → 1 (base case)
Collapse upward:
fac(2) = 2 * 1 = 2
fac(3) = 3 * 2 = 6
fac(4) = 4 * 6 = 24
Call stack at deepest point: frames for fac(4), fac(3), fac(2), fac(1) stacked. After base case, frames pop in reverse order.
Python flavor:
def fac(n):
if n <= 1:
return 1
return n * fac(n - 1)
C++ flavor:
int fac(int n) {
if (n <= 1) return 1;
return n * fac(n - 1);
}
Countdown Trace (Side Effects)
Not every recursive example returns a product; some exams ask how many prints occur.
FUNCTION boom(n)
IF n == 0 THEN
PRINT "done"
RETURN
END IF
PRINT n
boom(n - 1)
END FUNCTION
boom(3)
Output order: 3, then 2, then 1, then done. Prints happen before the recursive call, so they appear descending. If PRINT n were placed after boom(n-1), you would see done first, then 1, 2, 3 on the way back up — placement relative to the recursive call is a frequent trap.
Common Recursion Traps
| Trap | What happens | Fix / recognition |
|---|---|---|
| Missing base case | Infinite recursion; stack grows until failure | Every path must eventually hit a non-recursive return |
| Base case never reached | e.g. always call fac(n) instead of fac(n-1) | Argument must move toward the base |
| Wrong base value | e.g. fac(1) returns 0 | Yields wrong numeric answer with finite stack |
| Off-by-one in base | n == 0 vs n <= 1 for factorial | Check the mathematical definition used in the stem |
| Confusing with loops | Same problem often solvable iteratively | Recursion questions want stack/base-case reasoning |
Broken example (infinite):
FUNCTION bad(n)
RETURN n * bad(n) # never decreases; no base case
END
Broken example (finite but wrong):
FUNCTION bad_fac(n)
IF n == 0 THEN RETURN 0 # should return 1 for 0!
RETURN n * bad_fac(n - 1)
END
bad_fac(3) → 3 * 2 * 1 * 0 = 0. The stack behaved, but the base case lied.
Worked Mixed Example: Recursion + Control Flow
FUNCTION sum_odd(n)
IF n <= 0 THEN
RETURN 0
END IF
IF n % 2 == 0 THEN
RETURN sum_odd(n - 1)
ELSE
RETURN n + sum_odd(n - 1)
END IF
END FUNCTION
Trace sum_odd(5):
- 5 odd →
5 + sum_odd(4) - 4 even →
sum_odd(3) - 3 odd →
3 + sum_odd(2) - 2 even →
sum_odd(1) - 1 odd →
1 + sum_odd(0) - 0 base →
0
Collapse: 1+0=1, then 3+1=4, then 5+4=9. So sum_odd(5) = 9 (1+3+5).
This blends modulo checks from operators, branching from control flow, and stack unwinding from recursion — representative of harder Cyber Test programming items.
Connecting Back to Earlier Sections
- Variables/types: parameters are local variables; return types matter when mixing ints and bools in conditions.
- Operators: recursive cases are often
n * f(n-1)orn + f(n-1); modulo selects branches. - Control flow: base cases are
ifs; without them, recursion never stops — the same discipline as loop termination conditions.
Exam Habits
- Find the base case first; evaluate it cold.
- Expand calls until the base, then substitute returns upward.
- Count stack depth only if asked; otherwise focus on returned values or printed order.
- Ask whether the recursive argument shrinks toward the base every time.
- Watch print/return placement before vs after the recursive call.
With variables, control flow, and functions/recursion in place, you have the language-agnostic core of the programming domain. Language-specific syntax and debugging patterns build on this foundation in the following chapter.
What does fac(3) return for: fac(n) returns 1 if n <= 1, else returns n * fac(n - 1)?
Which statement best describes a correct recursive design?
For boom(n): if n == 0 print "done" and return; else print n then call boom(n-1). What is the output of boom(2)?
When main calls helper(5), and helper calls util(5) before returning, which call-stack picture is correct at the deepest moment?