Recursion, Debugging, and Final Code Reading

Key Takeaways

  • A recursive function needs a reachable base case, or Python eventually raises RecursionError.
  • Each recursive call has its own local variables even when the parameter names are identical.
  • Debugging a snippet means finding the first failing expression on the executed path, not guessing from the last line.
  • Operation type predicts the exception: indexing problems give IndexError/TypeError, conversions give ValueError, lookups give KeyError.
  • A consistent final-review routine traces calls, scopes, return values, printed output, and exception paths in a fixed order.
Last updated: June 2026

Recursion at entry level

Recursion means a function calls itself. PCEP does not require advanced algorithms, but you must recognize a base case, a recursive case, and the order returned values combine. Without a reachable base case the calls continue until Python hits its recursion limit (1000 by default) and 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 chain is countdown(3), countdown(2), countdown(1), countdown(0). Returns combine on the way back up, producing '321go'. Read recursion in two directions: descend to the base case, then unwind the returns upward. Most wrong answers come from trying to combine values on the way down instead of on the way back.

Each call owns its locals

Recursive calls reuse the same parameter name, but each call has its own copy. They do not share one variable.

CallLocal nReturns
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'

Reading bottom-up: countdown(0) gives 'go'; countdown(1) gives '1go'; countdown(2) gives '21go'; countdown(3) gives '321go'. This stacked-table method works for any recursive snippet that asks for final output. It also visually reinforces that the deepest call returns first while the outermost call returns last.

Debugging means first failure on the executed path

A debugging item may show code with several plausible problems. Python stops at the first unhandled exception on the path actually executed; later potential errors are irrelevant 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, raising IndexError. The program crashes there; the second call, which would raise KeyError, is never reached. If a question asks which exception the program raises, the answer is IndexError, not KeyError, precisely because order of execution decides which fires first.

This is why guessing from the last line is dangerous: the last line may never run. Always trace from the first top-level executable statement forward until something fails.

Sorting exception clues, and a final routine

Use the operation to predict the exception type:

Clue in the snippetLikely exception
Undefined / unbound variableNameError / UnboundLocalError
Bad argument count or incompatible operand typesTypeError
Failed conversion from a valid typeValueError
Sequence position does not existIndexError
Dictionary key does not existKeyError
Division or modulo by zeroZeroDivisionError

For example, [1, 2]['0'] raises TypeError (a list index must be an integer or slice, not a string), while [1, 2][9] raises IndexError (index type valid, position absent).

Final code-reading routine for the last minutes before the exam:

  1. Read all defs as definitions, not executions.
  2. Start at the first top-level executable line.
  3. For each call, bind parameters and trace its fresh local scope.
  4. Record printed output separately from returned values.
  5. Stop at the first unhandled exception, but still run any matching except and finally clauses.
  6. For recursion, descend to the base case, then unwind the returns.

Label every value as output, return value, local variable, or exception. One missed None, one wrong handler order, or one skipped finally block can flip the entire answer, and at 70% to pass, every traced item counts.

Recursion that accumulates, and a mixed worked example

Not all recursion concatenates strings; many PCEP items accumulate a number. The same descend-then-unwind logic applies.

def summ(n):
    if n == 0:
        return 0
    return n + summ(n - 1)

print(summ(3))

Descend: summ(3) waits on summ(2), which waits on summ(1), which waits on summ(0) returning 0. Unwind: summ(1) is 1 + 0 = 1; summ(2) is 2 + 1 = 3; summ(3) is 3 + 3 = 6. The printed value is 6. If the base case were forgotten, summ(-1) would recurse forever and raise RecursionError, so always confirm the base case is actually reachable for the given input.

A mixed snippet combining the whole chapter:

total = 0

def apply(values, key):
    return values[key] * 2

data = [10, 20]
try:
    print(apply(data, 1))
    print(apply(data, 5))
except IndexError:
    print('out of range')
finally:
    print('cleanup')

Trace it in order: apply(data, 1) returns 20 * 2 = 40, printed. apply(data, 5) indexes past the end and raises IndexError; the matching except prints out of range; then finally prints cleanup. The full output is 40, out of range, cleanup.

StepActionOutput so far
1apply(data, 1) returns 4040
2apply(data, 5) raises IndexError(none added)
3except IndexError runsout of range
4finally runscleanup

That single example exercises call flow, argument binding, an IndexError, and finally, exactly the kind of integration the last exam questions demand.

Test Your Knowledge

What is the purpose of a base case in a recursive function?

A
B
C
D
Test Your Knowledge

Which exception is most likely raised by {'x': 1}['y']?

A
B
C
D
Test Your Knowledge

A call inside a try block raises IndexError, a matching except IndexError prints 'fixed', and a finally block prints 'done'. If no new exception is raised, what happens next?

A
B
C
D
Congratulations!

You've completed this section

Continue exploring other exams