Consolidating Advanced Programming Techniques

advanced30 min

Learning objectives

  • Integrate exception handling, stack-frame understanding and recursion within larger programs

Learn

AQA 4.1.1.9, .15, .16 — Consolidating advanced programming techniques

Retrieval: this sequence has covered exception handling, the call stack, and recursion as separate topics, each in its own lesson. Real programs never keep them this neatly separated — a single robust, recursive function has to get its base case right and handle whatever invalid input reaches it, at the same time. This final lesson brings all three together.

Understand — why integration is harder than any one technique alone

A function that's recursive AND must validate its own input has to get both things right simultaneously: an exception raised partway through a deep recursive call still has to unwind cleanly back through every stack frame above it, and a base case has to account for genuinely invalid input, not just the "normal" smallest case. Treating these as separate concerns, bolted on independently, is exactly where combined bugs hide.

Worked example — a robust recursive function

def digit_sum(n):
    if not isinstance(n, int) or n < 0:
        raise ValueError("digit_sum requires a non-negative integer.")
    if n < 10:                      # base case: single digit
        return n
    return n % 10 + digit_sum(n // 10)   # recursive case: last digit + the rest

def safe_digit_sum(value):
    try:
        return digit_sum(value)
    except ValueError as e:
        return f"Could not calculate: {e}"

print(safe_digit_sum(2024))    # 8  (2+0+2+4)
print(safe_digit_sum(-5))      # "Could not calculate: digit_sum requires a non-negative integer."
print(safe_digit_sum("abc"))   # "Could not calculate: digit_sum requires a non-negative integer."

Notice the validation happens once, at the very top of every call — including every recursive call — rather than being checked only by the outer wrapper. This matters: without it, a single bad recursive call partway through (not just the very first call) could still slip through unvalidated.

Diagnose the category — three broken versions, three different faults

Each version below of a recursive function list_depth(value) (meant to return how deeply nested a list is — a plain value is depth 0, a list containing only plain values is depth 1, and so on) contains exactly one fault. For each, diagnose which category the fault belongs to (missing/incorrect base case, unhandled exception, or a scope/stack-frame misunderstanding) before fixing it:

Version A:

def list_depth(value):
    if not isinstance(value, list):
        return 0
    return 1 + max(list_depth(item) for item in value)

Calling list_depth([]) crashes with ValueError: max() arg is an empty sequence.

Version B:

def list_depth(value):
    depth = 0
    if not isinstance(value, list):
        return depth
    return 1 + max(list_depth(item) for item in value)

print(list_depth([1, [2, 3]]))
print(depth)

The print(depth) line crashes with NameError: name 'depth' is not defined.

Version C:

def list_depth(value):
    if not isinstance(value, list):
        return 0
    if len(value) == 0:
        return 0
    return 1 + max(list_depth(item) for item in value[1:])

list_depth([1, 2, 3]) returns 1 — correct. list_depth([[1, 2], [3, 4]]) returns 1 — should be 2.

(Version A: missing/incorrect base case — an empty list has no items for max() to compare, so the "empty list" case needs its own explicit base case (return 0 or 1 depending on convention), not just "not a list". Version B: scope/stack-frame misunderstanding — depth is a local variable belonging only to list_depth's own stack frame; the outer print(depth) has no access to it, exactly the pattern from the Stack Frames lesson's debug task. Version C: an incorrect recursive case — value[1:] silently skips the very first item of every list at every level, so nesting inside that first item is never actually explored; the recursive case should use the whole value, not value[1:].)

Apply it — build a combined program

Write a function safe_factorial_sum(numbers) that takes a list of values, and for each one that is a non-negative integer, calculates its factorial recursively and adds it to a running total — skipping (not crashing on) any value in the list that isn't a valid non-negative integer, and printing a specific message naming which values were skipped and why.

Challenge

Extend safe_factorial_sum so that if the entire list contains no valid non-negative integers at all, it raises its own exception with a clear message, rather than silently returning 0 as if everything had gone fine — then write code that calls it inside a try/except and handles that case gracefully too.

Looking ahead: Sequence 11 (Programming Paradigms) introduces Object-Oriented Programming — a fundamentally different way of organising the kind of robust, multi-part programs this lesson has just practised combining.

Practise

Apply what you've just learned in the Coding Lab.

Open Coding Lab

Test yourself

Check your understanding with exam-style questions.

Go to Exam Practice
Log in to track this lesson on your progress dashboard.
Log in