Robust Programming & Exception Handling

intermediate25 min

Learning objectives

  • Explain runtime, syntax and logic errors
  • Distinguish input validation from exception handling
  • Use try, except and finally appropriately
  • Create meaningful error messages

Learn

AQA 4.1.1.9 — Exception handling

Retrieval: every program written across the whole of Year 12 quietly assumed nothing would go wrong — a file would exist, a number would really be a number, a division would never be by zero. Year 13 starts by dropping that assumption. This is the first lesson of the A-level's second year, and it asks: what does a program do when reality doesn't cooperate?

Key vocabulary

  • Syntax error — code that doesn't follow Python's grammar at all; caught before the program even runs.
  • Runtime error — syntactically valid code that fails while executing (dividing by zero, opening a file that doesn't exist).
  • Logic error — code that runs to completion without crashing but produces the wrong result — the hardest category to find, since there's no error message at all.
  • Exception — the object Python creates to represent a runtime error, which can be caught and handled instead of crashing the program.
  • Traceback — the report Python prints when an exception is never caught, showing exactly where in the code it happened.

Understand — three failure modes need three different responses

A syntax error must simply be fixed before the code can run at all — there's no "handling" it. A logic error needs testing and tracing to even notice it's happened, since the program runs happily to the wrong answer. A runtime error is different again: the code is correct in structure, but a specific situation at a specific moment causes it to fail — and Python gives you a mechanism to prepare for that situation in advance, so the whole program doesn't crash because of it.

See it — try / except / finally

try:
    number = int(input("Enter a number: "))
    result = 100 / number
    print(result)
except ValueError:
    print("That wasn't a valid number.")
except ZeroDivisionError:
    print("Can't divide by zero.")
finally:
    print("Done.")

Python attempts every line inside try. The moment one raises an exception, it jumps straight to the matching except block (skipping any remaining try lines entirely) and continues from after the whole try/except structure. finally runs unconditionally — whether an exception occurred or not — which is why it's the right place for cleanup code like closing a file.

Input validation vs exception handling — not the same thing

Validation checks input before using it, rejecting bad input proactively, in a situation the program can fully control:

age = int(input("Age: "))
while age < 0 or age > 120:
    age = int(input("Enter a realistic age: "))

Exception handling catches failures after they occur, for situations the program cannot fully control in advance — a file that might or might not exist, a network request that might fail:

try:
    with open("data.txt") as f:
        contents = f.read()
except FileNotFoundError:
    print("data.txt was not found - creating a new one.")
    contents = ""

Validation asks "is this input acceptable?" before acting; exception handling asks "did that action actually work?" after attempting it — genuinely different questions, needed for genuinely different situations.

Debug it — diagnose, explain, fix, test, justify

The following program is meant to read two numbers from the user and print their average, refusing to crash on bad input:

def average_of_two():
    a = int(input("First number: "))
    b = int(input("Second number: "))
    try:
        return (a + b) / 2
    except ValueError:
        return "Please enter valid numbers."

print(average_of_two())

Entering abc for the first number still crashes the program with an unhandled ValueError traceback.

  1. Diagnose: run it (or trace it by hand) with non-numeric input and locate exactly which line raises the exception.
  2. Explain: why does the existing try/except block fail to catch it, even though a ValueError is exactly the exception type it's listening for?
  3. Fix: rewrite the function so the risky conversions themselves are inside the try block.
  4. Test: confirm your fixed version handles non-numeric input for either field without crashing, and still returns the correct average for valid input.
  5. Justify: explain in one sentence why where a line sits relative to try/except matters just as much as which exception type is named.

(The int() conversions happen before the try block even starts, so a ValueError there is never caught — it's not that the except clause names the wrong exception, it's that the risky lines aren't protected by try at all. Moving both int(input(...)) calls inside try fixes it.)

Common mistake

Writing except: pass (or catching Exception generically and doing nothing useful with it) — this silently swallows every error, including genuine bugs that have nothing to do with the situation you meant to handle, making them far harder to find later. AQA's NEA marking criteria explicitly reward "good exception handling" as an excellent coding style characteristic (Table 2) — but only when handlers are specific and meaningful, never a blanket catch-all that hides problems instead of solving them.

Check your understanding

A program has except ValueError: listed before except Exception: for the same try block. A TypeError occurs. Which block catches it, and why? (Neither ValueError block catches it — Python checks except clauses in order and TypeError doesn't match ValueError, so it falls through to the general Exception clause, which does match. If the order were reversed, the general Exception clause would swallow every error, including the ValueError, silently — order matters.)

Challenge

Take a program from earlier in the course that uses input() and add exception handling so it never crashes on invalid input, no matter what the user types — then deliberately test it with input designed to break it, and confirm it now fails gracefully instead.

Looking ahead: the next lesson (Advanced Exception Handling) goes further — handling several different, genuinely distinct failure types within the same robust program, not just one at a time.

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