skip to content

try / except / else / finally Flow

The full statement flow: which clause runs when, why else exists, that the as variable is deleted after the block, and bare raise for re-raising. Interviewers test precise control-flow reasoning.

part ofPythonoverview, primer and where to startread it →
on this pageshow

questions

4

What order do a Python try statement's except, else and finally clauses run in?

level: juniorimportance: must knowfreq 72%

answer

  1. Four clauses, one pass through the statement
  2. Exactly one of except or else ever runs
  3. One clause is guaranteed on every path
  4. else means the body raised nothing
  5. finally runs before an exception keeps propagating

basics

~20 s

The try body runs first. If it raises, the first matching except clause runs and else is skipped; if it does not raise, else runs. The finally clause runs last on every path, including while an exception is still propagating.

solid answer

~40 s

The `try` body runs first. If it raises, Python tests the `except` clauses in source order and runs the first matching one, and the `else` clause is skipped. If the body finishes without raising, `else` runs instead of any handler. Either way `finally` runs last, and it also runs when no `except` clause matched and the exception is propagating outward, and when the body exited through `return`, `break` or `continue`. The point of `else` is to keep the success path *outside* the guarded region: an exception raised in `else` is not offered to that same statement's `except` clauses, so a handler for `KeyError` cannot silently swallow a `KeyError` from the follow-up work. A statement needs at least one `except` or a `finally`; `try`/`else` with neither is a `SyntaxError`.

code

python · 14 lines
python
def run(fail):
    try:
        print("try")
        if fail:
            raise ValueError("boom")
    except ValueError:
        print("except")
    else:
        print("else")
    finally:
        print("finally")

run(False)   # try, else, finally
run(True)    # try, except, finally

go deeper

for a junior

Be ready to state the order out loud without hesitating: try, then either a matching except or else, then finally. Knowing that exactly one of except and else runs is the whole answer at this level.

for a middle

An interviewer expects you to explain the mechanics: clauses are tested in source order and only the first match runs, else is skipped whenever the body raised or exited early, and finally runs even while an unhandled exception is propagating.

for a senior

Show the judgment behind the shape: keep the try body down to the one operation that can fail, put recovery in except and the success path in else so a handler cannot swallow an identical exception from unrelated code.

for a principal

Own the convention across a codebase — where teams standardize the guarded region, how error handling shows up in review checklists and linter configuration, and when a repeated try/finally should become a reusable context manager instead.

A Python `try` statement can carry four kinds of clause, and this question is simply about which of them runs, in what order, on each path through the body. ## The four clauses - `try:` holds the guarded block. - Each `except` clause names an exception type, or a parenthesized tuple of types, and handles a matching exception. There can be several. - `else:` is optional and runs only when the `try` block finished without raising. - `finally:` is optional and runs on the way out of the whole statement, whatever happened. The grammar requires at least one `except` clause or a `finally` clause. An `else` clause is only legal when at least one `except` clause is present: `try` / `else` / `finally` with no handler is a `SyntaxError`, reported on CPython 3.14 as `expected 'except' or 'finally' block`. ## The four paths **Path 1 — the body completes normally.** The `else` clause runs, then `finally`. No handler runs. **Path 2 — the body raises and a handler matches.** Python walks the `except` clauses in source order and runs the body of the first clause whose type matches the raised exception's class (an exact class or any base of it). `else` is skipped entirely. Then `finally` runs. Order matters: the *first* match wins, not the closest one, so a broad clause written above a narrow one makes the narrow one unreachable. **Path 3 — the body raises and nothing matches.** No handler body runs and `else` is skipped, but `finally` still runs, and only then does the exception continue outward to the enclosing frame. This is the guarantee people most often get wrong: `finally` is not "the code that runs after the exception was handled", it is the code that runs on the way out no matter what. **Path 4 — the body exits with `return`, `break` or `continue`.** There was no fall-through to `else`, so `else` is skipped. `finally` still runs before control actually leaves. ## Why `else` exists Without `else` there are only two places to put the code that should run when nothing went wrong: inside the `try` body, or after the whole statement. Both have problems. Inside the body it is *guarded*, which is the trap: ```python try: row = table[key] process(row) # a KeyError in here is caught below too except KeyError: row = None ``` If `process` raises a `KeyError` of its own, the handler swallows it and the bug is invisible. Moving it to `else` narrows the guarded region to the single operation you actually expect to fail: ```python try: row = table[key] except KeyError: row = None else: process(row) # its own KeyError propagates ``` An exception raised in the `else` clause is **not** offered to that statement's own `except` clauses. It propagates to an enclosing handler — after that statement's `finally` has run. Putting the success-path code *after* the whole statement is different again: trailing code also runs when a handler caught the exception and recovered. `else` runs only when nothing was raised. When the handler leaves the function (`return`, `raise`) the two coincide, which is why plenty of code never needs `else`; when the handler falls through, they differ, and reaching for `else` states the intent directly. ## Where `finally` sits `finally` is the last thing to run in every case, including after the `except` body. That ordering matters when the handler produces something the cleanup needs to see: the handler has already finished by the time `finally` starts. It also runs while an exception is in flight, which is what makes `try`/`finally` a usable release-the-resource construct. ## A worked trace ```python def run(fail): try: print("try") if fail: raise ValueError("boom") except ValueError: print("except") else: print("else") finally: print("finally") ``` `run(False)` prints `try`, `else`, `finally`. `run(True)` prints `try`, `except`, `finally`. Exactly one of `except` and `else` ever runs, and `finally` always closes the statement. ## Things that are easy to get backwards - `else` is not "the code after the handler" — it is the *no exception was raised* branch. - `finally` is not skipped when the exception is unhandled; the exception waits for it. - Only one `except` clause ever runs, even if several would match. - A `finally` that itself raises replaces the in-flight exception with the new one. This flow is one of the oldest stable corners of the language, so an interviewer asking it is checking that you can reason about control flow precisely, not that you have memorized a recent change.

  • Why move the success-path code into an else clause instead of leaving it at the end of the try body?
    To narrow the guarded region. Code in the `try` body is protected by every `except` clause below it, so a follow-up call that raises the same type is silently swallowed by a handler meant for the risky line above it. Code in `else` runs only when the body raised nothing, and its own exceptions propagate past that statement's handlers. The `try` body should hold the single operation you expect to fail.
  • Does finally still run when no except clause matches the raised exception?
    Yes. Python runs `finally` before the exception continues outward to the enclosing frame, so cleanup happens even on a completely unhandled error. The exception is not lost — it resumes propagating once `finally` finishes, unless `finally` raises something of its own, which replaces it.
  • What happens to the else clause when the try body executes a return?
    It is skipped. `else` runs only when the body falls off its own end without raising; `return`, `break` and `continue` all leave the body before that point. The `finally` clause still runs before control actually leaves the statement.

Think of except and else as the two exits from a checkpoint — you take one or the other, never both — while finally is the door you leave through either way.

saying these in an interview costs you the question

  • Says else runs after every try, exception or not
  • Thinks finally is skipped when the exception is unhandled
  • Believes code in else is protected by the except clauses
  • Claims else runs when the try body returns
  • Says every matching except clause runs, not just the first
  • Writes try/else with no except clause

context

open as a page

How does Python choose which except clause handles a raised exception?

level: middleimportance: should knowfreq 50%

basics

~20 s

Python tests the except clauses top to bottom and runs the first whose named class is the raised exception's class or a base of it. Only that clause runs; a parenthesized tuple matches any of several types.

open as a page

Inside an except block, what does a bare `raise` statement re-raise?

level: seniorimportance: should knowfreq 44%

basics

~10 s

A bare raise re-raises the exception currently being handled, taken from the interpreter's own exception state rather than from any variable. Outside an active handler it raises RuntimeError: No active exception to reraise.

open as a page

Why is the name in `except ValueError as err` undefined after the except block?

level: middleimportance: nice to knowfreq 24%

basics

~20 s

Python 3 compiles the handler as if it ended with del err, so the binding is gone once the block exits — breaking the cycle from the exception through its traceback to the frame. Copy it elsewhere to keep it.

open as a page