Skip to content

Python · Errors & Exceptions

Python try/except/else/finally Explained — Catching Exceptions the Right Way

4 min read Updated 2026-06-19 Share:

Practice try / except / else / finally interview questions

Python exception handling, explained

Exceptions are how Python signals that something went wrong. The try statement has four clauses — try, except, else, finally — and knowing exactly what each does, and in what order they run, is what separates robust error handling from code that silently hides bugs.

The four clauses and their order

try holds the risky code. except handles a matching exception. else runs only if no exception was raised. finally runs always, exception or not.

try:
    value = int(user_input)        # might raise ValueError
except ValueError:
    print("not a number")
else:
    print(f"got {value}")          # runs only if no exception
finally:
    print("done")                  # always runs

The flow: try → if it raises, a matching except → otherwise else → and finally last, no matter what. Putting the success-path code in else keeps the try block as small as the operation that can actually fail.

Catch specific exceptions, not everything

A bare except: (or except Exception:) catches far too much and hides real bugs. Catch the narrowest exception that you can actually handle.

# Bad — swallows typos, KeyboardInterrupt-adjacent bugs, everything
try:
    risky()
except:
    pass

# Good — only the error you expect and can recover from
try:
    config = json.loads(text)
except json.JSONDecodeError as e:
    print(f"bad config: {e}")

Letting an unexpected exception propagate is usually better than catching it — it gives you a traceback instead of a mysterious wrong result later.

Handling multiple exception types

You can list several types in one tuple, or use separate clauses when each needs different handling. The first matching except wins, so order from specific to general.

try:
    result = process(data)
except (KeyError, IndexError) as e:
    print(f"missing data: {e}")
except ValueError as e:
    print(f"bad value: {e}")
except Exception as e:
    print(f"unexpected: {e}")
    raise                          # re-raise what you can't handle

Because subclasses match parent clauses, a more general except placed first would shadow the specific ones below it.

finally always runs — even on return

finally executes even if the try block returns, breaks, or raises. That makes it the place for cleanup that must happen no matter what (though context managers are usually cleaner).

def read():
    f = open("data.txt")
    try:
        return f.read()            # finally still runs before the return completes
    finally:
        f.close()                  # guaranteed, even on exception

A subtle trap: a return inside finally will override any return or exception from the try block — avoid it.

Exception chaining with raise from

When you catch one error and raise another, use raise ... from to preserve the original cause. This keeps the full story in the traceback ("during handling of the above, another occurred").

try:
    config = load(path)
except FileNotFoundError as e:
    raise RuntimeError("config missing") from e   # chains the cause

Without from, Python still shows the implicit context, but from e states the relationship explicitly. Use from None to deliberately suppress a noisy original cause.

Accessing exception details

The as e binding gives you the exception object. You can read its args, its message, and (3.11+) attach notes for richer diagnostics.

try:
    int("abc")
except ValueError as e:
    print(type(e).__name__, e)     # ValueError invalid literal for int()...
    e.add_note("while parsing user input")   # 3.11+
    raise

Note that e is deleted at the end of the except block, so assign it to another name if you need it afterwards.

Recap

try/except/else/finally: risky code, the handler, the no-exception path, and the always-runs cleanup — in that order. Catch the most specific exception you can actually handle and let the rest propagate so you keep real tracebacks; never use a bare except: pass. finally runs even through return and exceptions (but don't return inside it). Use raise NewError(...) from original to chain causes, and the as e binding to inspect details. Good error handling is narrow, intentional, and never silent.

More ways to practice

The self-quiz is live. Get notified when mock interviews and new question packs drop.

or
Join our WhatsApp Channel