DEV Community

Cover image for Python Exception Handling: Tracing try/except/else/finally Blocks
Ameer Abdullah
Ameer Abdullah

Posted on

Python Exception Handling: Tracing try/except/else/finally Blocks

The execution order of exception handling blocks is non-obvious and regularly tested in senior interviews

Most developers use try/except regularly. Fewer can predict the exact execution order when you combine try, except, else, and finally in a single block, especially when return statements or nested exceptions are involved.


The Complete Structure

try:
    # code that might raise an exception
except SomeException:
    # runs if SomeException is raised in try
except AnotherException:
    # runs if AnotherException is raised in try
else:
    # runs if NO exception was raised in try
finally:
    # ALWAYS runs, exception or not
Enter fullscreen mode Exit fullscreen mode

The else block is the least known. It runs only when the try block completes without raising any exception. It is useful for code that should run only on success but is not part of the operation you are protecting.


Basic Execution Order Traces

Case 1 — No exception:

try:
    x = 1 + 1
except ValueError:
    print("value error")
else:
    print("else block")
finally:
    print("finally block")
Enter fullscreen mode Exit fullscreen mode

Output:

else block
finally block
Enter fullscreen mode Exit fullscreen mode

The try block succeeds. The except block is skipped. The else block runs. The finally block runs.

Case 2 — Exception raised and caught:

try:
    x = int("not a number")
except ValueError:
    print("value error caught")
else:
    print("else block")
finally:
    print("finally block")
Enter fullscreen mode Exit fullscreen mode

Output:

value error caught
finally block
Enter fullscreen mode Exit fullscreen mode

The try block raises ValueError. The except block catches it. The else block is skipped because an exception occurred. The finally block runs.

Case 3 — Exception not caught:

try:
    x = 1 / 0
except ValueError:
    print("value error")
else:
    print("else block")
finally:
    print("finally block")
Enter fullscreen mode Exit fullscreen mode

Output:

finally block
ZeroDivisionError: division by zero
Enter fullscreen mode Exit fullscreen mode

The ZeroDivisionError is not caught by the except clause. The finally block still runs before the exception propagates.


The return Inside finally

def tricky():
    try:
        return "from try"
    finally:
        return "from finally"

print(tricky())
Enter fullscreen mode Exit fullscreen mode

Output: from finally

This is a genuine interview trap. The finally block executes even when there is a return in the try block. The finally return overrides the try return. This behavior is technically correct but widely considered bad practice.


Exception Chaining

try:
    try:
        x = 1 / 0
    except ZeroDivisionError:
        raise ValueError("converted error") from None
except ValueError as e:
    print(f"caught: {e}")
    print(f"cause: {e.__cause__}")
Enter fullscreen mode Exit fullscreen mode

Output:

caught: converted error
cause: None
Enter fullscreen mode Exit fullscreen mode

raise X from None suppresses the exception chain, meaning the original ZeroDivisionError is not preserved as the cause. Without from None, the original exception would be accessible through e.__cause__.


The Interview Question

def process(data):
    result = []
    for item in data:
        try:
            result.append(10 / item)
        except ZeroDivisionError:
            result.append(None)
        else:
            print(f"processed {item}")
        finally:
            print(f"done with {item}")
    return result

output = process([2, 0, 5])
print(output)
Enter fullscreen mode Exit fullscreen mode

Trace each iteration:

item=2: 10/2 = 5, appended. else runs: "processed 2". finally runs: "done with 2". item=0: ZeroDivisionError. except runs: appends None. else is skipped. finally runs: "done with 0". item=5: 10/5 = 2, appended. else runs: "processed 5". finally runs: "done with 5".

Output:

processed 2
done with 2
done with 0
processed 5
done with 5
[5.0, None, 2.0]
Enter fullscreen mode Exit fullscreen mode

Practice exception handling trace problems at pycodeit.com.


Top comments (0)