Study guide

Recursion, Debugging, and Final Code Reading

PCEP Certified Entry-Level Python Programmer Study Guide Exam

Track your progress as you read

Finished sections get a check mark.

1 PCEP Exam Map and Study Strategy

Current PCEP-30-02 Facts How PCEP Tests Code Reading Official Resources and Ethical Prep Four-Week Study Plan and Readiness

2 Computer Programming and Python Fundamentals

1.1 Lexis, Syntax, Semantics, and Indentation 1.2 Identifiers, Keywords, Comments, and Basic Program Structure 1.3 Literals, Types, and Numeral Systems 1.4 Operators, Precedence, and Type Conversion 1.5 print(), input(), and Console Tracing

3 Control Flow

Boolean Expressions, Comparisons, and Truthiness If, Elif, Else, and Decision Trees While Loops and Sentinel Patterns For, Range, and Iteration Patterns Break, Continue, Pass, Loop Else, and Nesting

4 Data Collections

Indexing, Slicing, and Sequence Rules Lists, Mutation, Methods, and Aliasing Tuples, Immutability, and Packing Dictionaries, Keys, Membership, and Iteration Strings, Sets, and Collection Choice

5 Functions and Exceptions

def, return, None, and Call Flow Parameters, Arguments, Defaults, and Keywords Scope, Shadowing, global, and Local Errors Exception Types, try, except, else, and finally Recursion, Debugging, and Final Code Reading

Recursion, Debugging, and Final Code Reading

Key Takeaways Explain This Topic AI

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.

  • Call Local n Returns
  • 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 snippet Likely exception
  • Undefined / unbound variable NameError / UnboundLocalError
  • 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
  • Division or modulo by zero ZeroDivisionError

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:

Read all def s as definitions, not executions.

Start at the first top-level executable line.

For each call, bind parameters and trace its fresh local scope.

Record printed output separately from returned values.

Stop at the first unhandled exception, but still run any matching except and finally clauses.

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 .

  • Step Action Output so far
  • 1 apply(data, 1) returns 40 40
  • 2 apply(data, 5) raises IndexError (none added)
  • 3 except IndexError runs out of range
  • 4 finally runs cleanup

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