What's the purpose of the `else` and `finally` clauses in a `try` statement?

5 minbeginnerexceptionstry-except-else-finally

Quick Answer

`finally` always runs — on success, on a caught exception, on an uncaught exception, or even on `return`/`break`/`continue` inside the `try` — making it the right place for guaranteed cleanup. `else` runs **only if the `try` block completed with no exception**, and is used to separate "code that might raise" from "code that should only run if it didn't," keeping the `try` block itself minimal and making it clear which lines are actually being guarded.

Detailed Answer

finally: guaranteed cleanup

def read_config(path):
    f = open(path)
    try:
        return f.read()
    finally:
        f.close()    # ALWAYS runs -- success, exception, or even an early return

finally runs no matter how the try block exits: normal completion, a caught exception, an uncaught exception (it still runs before the exception propagates further), or control-flow statements like return inside the try. This unconditional guarantee is why it's the standard place for releasing resources (though in practice, a context manager — with open(path) as f: — is usually preferred over manual try/finally for exactly this pattern).

else: only on success, keeping the try block narrow

try:
    value = int(user_input)
except ValueError:
    print("not a valid number")
else:
    print(f"you entered {value}")   # only runs if int() did NOT raise
    process(value)                    # if process() raises, it's NOT caught by except ValueError above!

Without else, a common mistake is putting process(value) inside the try block itself — which means if process() also happens to raise a ValueError, it gets misattributed to "not a valid number" even though the real problem is unrelated to parsing. Moving success-dependent code into else keeps the try block scoped to exactly the operation you intend to guard, so unrelated exceptions from subsequent code aren't accidentally caught by the same handler.

Full combination

def process_file(path):
    try:
        f = open(path)
    except FileNotFoundError:
        print("file not found")
        return
    else:
        data = f.read()      # only runs if open() succeeded
    finally:
        try:
            f.close()          # always attempt cleanup...
        except NameError:
            pass                 # ...but f might not exist if open() failed
    return data

This shows the intended division of labor: try holds only the risky operation, except handles its specific failure, else holds what should happen only on success, and finally holds unconditional cleanup — each clause has one clear job instead of being crammed together.

Execution order guarantee

For a successful try: try body → else body → finally body. For a caught exception: try body (until the exception) → matching except body → finally body (note: else is skipped whenever an exception occurred, matched or not).

Interview-ready summary: finally is unconditional cleanup that runs regardless of outcome; else runs only when the try block succeeded, letting you separate "the operation that might fail" from "what happens next only if it didn't" — which avoids accidentally catching unrelated exceptions raised by follow-up code that has nothing to do with the original try.