Back to Blog
Python

Python Try Except Else Finally: Execution Order

python try except else finally: Understand exactly when the else and finally clauses run in Python's try/except statement, and how to use them for precise error handling.

exception handlingpython control flowtry except finallyerror handlingpython syntax
Flow diagram showing the execution order of try, except, else, and finally clauses in a Python exception handling block

Python's try statement can carry up to four clauses: try, except, else, and finally. Most developers use try and except regularly, but the else and finally clauses are frequently misunderstood. The key question is: when exactly does each clause run, and what guarantees does the language actually provide?

How the Four Clauses Execute in Order

The execution order of python try except else finally is deterministic. When a try block starts, Python evaluates the code inside it. If no exception is raised, the else clause runs immediately after the try block completes. The finally clause runs last, regardless of whether an exception was raised, handled, or left unhandled.

def read_config(path): try: with open(path) as f: return f.read() except FileNotFoundError: return "{}" else: print("config loaded successfully") finally: print("read_config finished")

If the file exists, the order is: try body, then else, then finally. If the file is missing, the order is: try body, then except, then finally. The else clause never runs when an exception was raised in the try block, even if that exception was handled by except.

What the the else Clause Actually Does

The else clause runs only when the the try block completes without raising any exception. This is useful for code that should execute only on success but should not be inside the try block itself.

Why does that distinction matter? Code inside the try block is protected by the except clauses. If you put success-only logic inside try, an exception raised by that logic would also be caught by the same except handler. That can hide bugs. The else clause is outside the protection of except, so an exception raised there propagates normally.

try: result = parse_user_input(raw) except ValueError: result = None else: validate_result(result) # exceptions here are NOT caught above

Here, validate_result runs only after successful parsing. If it raises, the except ValueError handler does not catch it, because the else clause is not part of the protected region. This separation keeps the error handling scope precise.

Why finally Runs Even When You Return

The finally clause is the only one that is guaranteed to run when the try block exits, no matter how it exits. That includes normal completion, a handled exception,, an un unhandled exception, a return statement, a break, or a continue.

def acquire_lock(): try: lock.acquire()\n return perform_operation() finally: lock.release()\n``` Even if `perform_operation` raises or returns early, `lock.release()` runs before the function actually returns to its caller. This is the primary reason `finally` exists: resource cleanup that must happen unconditionally. One subtlety: if the `finally` block itself raises an exception, that exception replaces whatever was being returned or raised. This can mask the original error. Keep cleanup code in `finally` simple and avoid operations that are likely to fail, such as network calls or complex parsing. ## Common Mistakes with else and finally A frequent mistake is using `else` when the intent is simply "run this after the try block." The `else` clause is not a general post-try hook; it is a success-only hook. If you need code to run regardless of success or failure, use `finally`. Another common mistake is placing cleanup logic in `except` rather than `finally`. If the `try` block succeeds, the `except` block never runs, and cleanup is skipped. The same cleanup must then be duplicated in the success path, which is error-prone. ```python # Wrong: cleanup only happens on failure try: conn = connect() data = query(conn) except ConnectionError: conn.close() # skipped on success raise # Right: cleanup always happens try: conn = connect() data = query(conn) finally: conn.close()

The second version guarantees conn.close() runs whether the query succeeds, fails, or the connection itself fails.

Nested try Blocks and Exception Propagation

When try blocks are nested, the else and finally clauses of the inner block run before the outer block's clauses. Exception propagation follows the same rule: an unhandled exception from an inner else clause moves to the nearest enclosing try/except.

try: try: risky_call() except ValueError: handle_value_error() else: success_hook() finally: outer_cleanup()

If risky_call() raises ValueError, the inner except handles it, then the outer finally runs. If success_hook() raises a different exception, it is not caught by the inner except ValueError, so it propagates to the outer try, which has no except here, and then to the caller. The outer finally still runs before the exception leaves the function.

Performance and Maintainability Tradeoffs

The try/except/else/finally structure has negligible runtime cost when no exception is raised. Python's exception handling is designed for the common case where exceptions do not occur. The real cost is in the except block itself: raising and catching an exception is significantly more expensive than a normal branch.

For maintainability, the else clause reduces the surface area of except. Keeping success-only logic out of the protected region means the except handler only deals with failures from the specific operation it was written for. This makes the error-handling contract easier to reason about during code review.

A practical guideline: use else when the success path contains logic that should not be accidentally caught by the same except. Use finally for any resource that must be released. Avoid putting large amounts of logic in finally, because an exception there replaces the original outcome and complicates debugging.

The full python try except else finally structure is most valuable in code that performs resource acquisition, network I/O, or multi-step parsing where the success path and failure path are genuinely different. For simple operations where the try block is a single function call, the else clause often adds little value and can be omitted.

python try except else finally: Practical Usage and Code Exa | RYUSLOG DEV