Recursion, Debugging, and Final Code Reading
Key Takeaways
- A recursive function must have a base case that can stop the chain of calls.
- Each recursive call has its own local variables, even when the parameter names are identical.
- Debugging PCEP snippets means identifying the first failing expression, not guessing from the final line.
- IndexError and KeyError are common collection errors; TypeError and ValueError often come from calls and conversions.
- A reliable final review routine traces calls, scopes, returned values, printed output, and exception paths in order.
Recursion in entry-level Python
Recursion means a function calls itself. PCEP does not require advanced algorithms, but you should recognize a base case, a recursive case, and the order in which returned values combine. Without a reachable base case, recursive calls continue until Python raises RecursionError.
def countdown(n):
if n == 0:
return 'go'
return str(n) + countdown(n - 1)
print(countdown(3))
The base case is n == 0. The calls are countdown(3), countdown(2), countdown(1), and countdown(0). The returns combine backward, producing 321go.
Each call has its own local scope
Recursive calls reuse parameter names, but they do not reuse one shared local variable. Each call has its own n.
| Call | Local n | Returns after deeper call |
|---|---|---|
countdown(3) | 3 | '3' + result of countdown(2) |
countdown(2) | 2 | '2' + result of countdown(1) |
countdown(1) | 1 | '1' + result of countdown(0) |
countdown(0) | 0 | 'go' |
This table method is useful whenever a recursive snippet asks for final output. First go down to the base case. Then walk back up and combine the return values.
Debugging means first failure
A debugging question may show code with several possible-looking problems. Python stops at the first unhandled exception on the executed path. Later errors do not matter if execution never reaches them.
def pick(data, key):
return data[key][0]
scores = {'Ava': []}
print(pick(scores, 'Ava'))
print(pick(scores, 'Noah'))
The first call finds key 'Ava', then tries index 0 of an empty list. That raises IndexError. The second call that would raise KeyError is never reached.
Sorting exception clues
Use the operation to identify the likely exception.
| Clue | Likely exception |
|---|---|
| Undefined variable | NameError |
| Bad argument count or incompatible operand types | TypeError |
| Failed conversion from a valid type | ValueError |
| Sequence position does not exist | IndexError |
| Dictionary key does not exist | KeyError |
For example, [1, 2]['0'] raises TypeError because list indexes must be integers or slices. [1, 2][9] raises IndexError because the index type is valid but the position is outside the list.
Final code-reading routine
Before the exam, practice with mixed snippets and use the same order every time:
- Read all def statements as definitions, not executions.
- Start at the first top-level executable line.
- For each call, bind parameters and trace local scope.
- Record print output separately from returned values.
- Stop at the first unhandled exception, but still run matching except and finally clauses.
- For recursion, trace down to the base case and then unwind returns.
During final review, also label every value as either output, return value, local variable, or exception. Many wrong answers mix these categories. A printed None may come from printing a return value, while a silent None may be assigned and never displayed.
This routine is slower than guessing, but it is faster than rereading the same snippet three times. PCEP rewards exactness: one missed None, one wrong handler order, or one skipped finally block can change the whole answer.
What is the purpose of a base case in a recursive function?
Which exception is most likely from {'x': 1}['y']?
A function call inside a try block raises IndexError, an except IndexError handler prints 'fixed', and a finally block prints 'done'. What happens next if no new exception is raised?
You've completed this section
Continue exploring other exams