Learn by reading through in order

Catching Exceptions with try / except

Catch Python errors at runtime with try / except and keep your program running — every example is runnable in your browser.

try / except is the mechanism that catches errors raised at runtime and keeps your program running instead of letting it crash. Use it for operations that are expected to fail sometimes — external data fetching, validating user input, file operations, and so on.

Syntax errors vs. runtime errors

Python has two broad kinds of errors. Syntax errors happen when the code violates Python's grammar and are caught before the program runs. Runtime errors (exceptions) are grammatically fine but surface while the program is running. Only runtime errors can be caught with try / except.

Syntax errors vs. runtime errors
Python errorSyntax error(caught before running)Runtime error(raised while running)Program never startstry can't catch itCan be caughtwith try / exceptmissing colondiv by zero

Syntax errors are caught the moment Python reads the code, so the program never runs. Runtime errors only fire when execution actually reaches that line — that's why try / except can intercept them.

# Syntax error: missing colon. Fails before the line even runs
# if x > 0
#     print("positive")

# Runtime error: grammatically fine, but fails at runtime
x = 100
print(x / 0)   # ZeroDivisionError: division by zero
print("this line never runs")

When a runtime error is raised, everything after that line is skipped. Run the next snippet and you'll see the final print() doesn't fire — the program stops at IndexError.

# Confirming a runtime error: without try, the program halts
numbers = [10, 20, 30]
print(numbers[10])       # IndexError: list index out of range
print("this line never runs")
# Output:
# Traceback (most recent call last):
#   File "...", line 3, in <module>
# IndexError: list index out of range

The basics of try / except

Put the risky code in the try: block and the recovery code in an except ErrorClass: block.

The instant an error fires in try, the rest of the try body is skipped and control jumps to the matching except. Code after except keeps running even when an error occurs.

How try / except flows
run the try bodydid an error fire?no error(ran to the end)error raised(rest skipped)except is skippedrun matching exceptcontinue with the restNoYesmatch

Run the try body. If no exception is raised, the except is skipped and control falls through to the rest of the program. If an exception fires, the remaining try body is skipped, the matching except runs, and then control continues afterward.

# Wrapping in try keeps the program running even when division by zero fires
a = 100
try:
    result = a / 0
    print(result)              # never reached
except ZeroDivisionError:
    print("Can't divide by 0")  # control jumps here

print("after the try block")    # always runs after try / except
# Output: Can't divide by 0
#        after the try block

Guard against accessing an out-of-range index in a stock list.

① Define stock = [12, 8, 3] (quantity on each shelf).

② First confirm what happens if you call stock[5] without try. The answer keeps this line as a comment; uncomment it to see the program stop with IndexError.

③ Now run print(stock[5]) inside try and print "That shelf doesn't exist" from except IndexError:.

④ After try / except, print "Continuing…" to confirm that execution resumes even after the exception.

(The explanation appears once the code runs correctly.)

Python Editor

Run code to see output

Capture the exception object with as e

Writing except ErrorClass as e: puts the exception's details in e. print(e) shows the error message, and type(e) shows the error class. Logging these is a solid starting point for tracing problems later.

user_input = "abc"   # supposed to be a number, but a string arrived

try:
    value = int(user_input)
except ValueError as e:
    print("Input isn't numeric")
    print(f"Details: {e}")
    print(f"Kind: {type(e).__name__}")
# Output: Input isn't numeric
#        Details: invalid literal for int() with base 10: 'abc'
#        Kind: ValueError
What you can pull out of the exception object
error firesint("abc")except ValueError as eexception object estr(e)→ messagetype(e).__name__→ class nametraceback.format_exc()→ stack traceassign

The message alone doesn't tell you where the error was raised, so during debugging it's standard to pair it with import traceback. traceback.format_exc() returns the call path (stack trace) at the moment the exception fired, and logging that makes root-cause analysis much easier.

import traceback

try:
    value = int("abc")
except ValueError as e:
    print(f"Details: {e}")
    print(f"Kind: {type(e).__name__}")
    print("--- traceback ---")
    print(traceback.format_exc())
# Output:
# Details: invalid literal for int() with base 10: 'abc'
# Kind: ValueError
# --- traceback ---
# Traceback (most recent call last):
#   File "...", line 4, in <module>
#     value = int("abc")
# ValueError: invalid literal for int() with base 10: 'abc'

When the user input can't be converted to a number, print the message, kind, and stack trace together.

① At the top, write import traceback and set user_input = "Tokyo".

② In try, call int(user_input), and catch it with except ValueError as e:.

③ Inside except, print "Message: {e} / Kind: {type(e).__name__}" on one line, then print(traceback.format_exc()).

Python Editor

Run code to see output

Multiple except blocks and the umbrella Exception

You can stack multiple except blocks on a single try. The one that matches first runs, so the idiom is to list them from most specific to most general.

Every runtime error inherits from Exception, so except Exception as e: catches anything, including unexpected errors.

Order except from specific to general
error in the try bodyexceptIndexError?run IndexErrorhandlerexceptZeroDivisionError?run ZeroDivisionErrorhandlerexceptException?catch-allfor unexpectedcontinueafter trymatchno matchmatchno matchmatch
numbers = [10, 20, 30]
index = 1
divisor = 0

try:
    value = numbers[index]
    print(value / divisor)
except IndexError as e:
    print(f"Index out of range: {e}")
except ZeroDivisionError as e:
    print(f"Can't divide by 0: {e}")
except Exception as e:
    print(f"Other error: {type(e).__name__}: {e}")
# Output: Can't divide by 0: division by zero

Don't put Exception first

Exception is the parent of almost every exception, so placing it at the top prevents any later, more specific except from ever running. Use it as the last safety net for unexpected errors.

Pull a number of items off a user's purchase history, sum them, and compute the average.

① Define history = [1200, 800, 1500] (purchase amounts) and take = 10 (number to take).

② Inside try, compute total = sum(history[:take]), then avg = total / len(history[:take]) and print(f"Average: {avg}").

③ Add a catch-all: except ZeroDivisionError as e: prints "No data", and except Exception as e: prints "Unexpected: {e}". (On the happy path the average prints normally.)

Python Editor

Run code to see output

Running cleanup with finally

The finally: block is the cleanup slot that always runs, whether or not an exception fires. Use it to release resources that must be closed — files, DB connections, and so on.

The else: block runs only when no exception was raised. It's not used as often, but it makes the "only on success, move on" path explicit.

Execution order of try / except / else / finally
run the try bodydid an exception fire?else block(success only)except block(handle it)finally block(always runs)continue with the restNo (success)Yes (failed)

After try runs: no exception → go to else, exception → go to the matching except. Either way, finally always runs at the end — that's the key.

# Happy path: else and finally run
a, b = 10, 2
try:
    result = a / b
except ZeroDivisionError:
    print("Caught div by zero")
else:
    print(f"Success: {result}")
finally:
    print("Running cleanup")
# Output: Success: 5.0
#        Running cleanup

print("---")

# Error path: except and finally run
a, b = 10, 0
try:
    result = a / b
except ZeroDivisionError:
    print("Caught div by zero")
else:
    print(f"Success: {result}")
finally:
    print("Running cleanup")
# Output: Caught div by zero
#        Running cleanup

Simulate an API call and log success, failure, and cleanup on every path.

① Define user_ids = [101, 102, 103] and target = 0.

② In try, grab user = user_ids[target] (don't print yet).

③ In except IndexError:, print "No such user".

④ In else:, print f"Fetched: {user}" so it runs only when no exception fired.

⑤ In finally:, print "Connection closed" so it runs on both paths.

Python Editor

Run code to see output

In this article you learned the basics of exception handling with try / except, how to capture exception info with as e, how to stack multiple except blocks and use Exception as a catch-all, and how to run cleanup with finally. Next up: raising exceptions yourself with raise, and defining custom exception classes.

QUIZ

Knowledge Check

Answer each question one by one.

Q1Which of these is an example of a syntax error?

Q2What does the following code print?
a = 100
try:
print(a / 0)
except ZeroDivisionError:
print("caught")
print("end")

Q3Of try / except / else / finally, which always runs regardless of whether an exception was raised?