Course topics

By WebNest Studio Engineering Team

Python Tutorial

Exception Handling in Python

An exception is an event that disrupts the normal flow of a program's instructions, typically raised when an error occurs — dividing by zero, accessing a missing dictionary key, or opening a file that doesn't exist. Rather than letting an unhandled exception crash the program with a traceback, Python lets you anticipate and handle specific error conditions gracefully using try/except blocks.

Exception handling is not just for "true" errors, either. It is also Python's standard mechanism for signaling and reacting to any exceptional condition your own code needs to communicate, through custom exception classes.

Why it matters

Files go missing, users type letters where numbers are expected, and networks fail. A program that does not handle these stops with a traceback; one that does can report the problem and carry on. Exception handling is how Python code deals with everything that can go wrong at runtime, and every production program relies on it.

try, except, else, and finally

A full exception-handling block has four parts, each with a distinct purpose.

  • try — contains the code that might raise an exception.
  • except ExceptionType: — runs only if that specific exception type (or a subclass of it) was raised in the try block; you can have multiple except clauses to handle different error types differently.
  • else — runs only if the try block completed with no exception at all; useful for code that should run only on success, without being part of what is guarded by except.
  • finally — always runs, whether an exception occurred or not, and even if the exception was not caught; commonly used for cleanup like closing files or network connections.

Raising Exceptions

You raise an exception explicitly with the raise keyword, either raising a built-in exception type like ValueError("message") or re-raising a caught exception with a bare raise inside an except block (which preserves the original traceback). Raising early with a clear message, rather than letting invalid data propagate silently, makes bugs far easier to diagnose.

Custom Exception Classes

For application-specific error conditions, you can define your own exception class by inheriting from Exception (or a more specific built-in exception). This lets calling code catch exactly your application's errors — such as InsufficientFundsError — separately from generic Python errors, and lets you attach extra data (like an account balance) to the exception object itself.

Examples

try/except/else/finally handling multiple error types

Python
def safe_divide(a, b):
    try:
        result = a / b
    except ZeroDivisionError:
        print("Cannot divide by zero.")
        return None
    except TypeError:
        print("Both arguments must be numbers.")
        return None
    else:
        print("Division succeeded.")
        return result
    finally:
        print("safe_divide finished.")

print(safe_divide(10, 2))
print(safe_divide(10, 0))
Output
Division succeeded.
safe_divide finished.
5.0
Cannot divide by zero.
safe_divide finished.
None

A custom exception class carrying extra data

Python
class InsufficientFundsError(Exception):
    def __init__(self, balance, amount):
        super().__init__(f"Cannot withdraw {amount}; balance is only {balance}")
        self.balance = balance
        self.amount = amount

def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientFundsError(balance, amount)
    return balance - amount

try:
    withdraw(100, 250)
except InsufficientFundsError as e:
    print(str(e))
    print(e.balance, e.amount)
Output
Cannot withdraw 250; balance is only 100
100 250

Common Mistakes

  • Writing a bare "except:" with no exception type, which silently catches everything — including typos and keyboard interrupts — and hides real bugs.
  • Putting too much code inside a single try block, making it unclear which line actually raised the exception that was caught.
  • Forgetting that finally runs even after a return statement inside try or except, which can be surprising when finally also contains a return that overrides the earlier one.
  • Creating a custom exception but forgetting to call super().__init__(message), losing the readable string representation when the exception is printed.

Exercise

Write safe_divide. It returns the result of the division, or None when the divisor is zero, and in both cases prints done before returning.

Expected output: done, 5.0, done, None (one per line)

Python · starter code
def safe_divide(a, b):
    # TODO: try the division and return the result
    # TODO: return None if a ZeroDivisionError occurs
    # TODO: print "done" in every case
    pass


print(safe_divide(10, 2))
print(safe_divide(10, 0))
Show hints
  • Catch the specific exception: except ZeroDivisionError:.
  • A finally block runs whether or not an exception occurred, even when the try block returns.
Show solution
Python · solution
def safe_divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return None
    finally:
        print("done")


print(safe_divide(10, 2))
print(safe_divide(10, 0))

Quick quiz

1. When does the else block of a try statement run?

  • A. When an exception occurred
  • B. When no exception occurred in the try block
  • C. Always
  • D. Never
Show answer

B. It holds the code that should run only after the try block has succeeded.

2. When does the finally block run?

  • A. Only after an exception
  • B. Only when there was no exception
  • C. In every case
  • D. Only if there is no except block
Show answer

C. It is used for clean-up, such as closing a file or a connection.

3. Why is a bare except: a bad idea?

  • A. It is a syntax error
  • B. It only catches ValueError
  • C. It is slow
  • D. It catches everything, including Ctrl+C and real bugs, and hides them
Show answer

D. Catch the specific exceptions you expect and can handle.

Interview questions

Explain try, except, else and finally.

Show answer

The try block holds the code that may fail. An except block runs when a matching exception is raised in it. The else block runs only if no exception was raised. The finally block runs in every case, whether there was an exception or not and even if the function returns, and is used to release resources.

What is the difference between a syntax error and an exception?

Show answer

A syntax error means the code is not valid Python, for example a missing colon; it is found before the program starts and nothing runs. An exception is raised while a syntactically correct program is running, for example dividing by zero or opening a missing file. Exceptions can be caught and handled with try; a syntax error must be corrected in the source.

Key Points to Remember

  • try/except/else/finally each serve a distinct role: guarded code, error handling, success-only code, and always-run cleanup.
  • raise creates a new exception; a bare "raise" inside except re-raises the current one with its original traceback intact.
  • Custom exceptions inherit from Exception (or a subclass) and let calling code catch application-specific errors precisely.
  • Avoid bare except clauses — always catch the most specific exception type that applies.

Practice in the playground

Write and run your own code for this topic, or start from the exercise or an example. Runs Python in your browser. Your code for each topic is kept in this browser.

Run the examples

Change an input, predict the result, then compare it with the output. Explain why the result changes.

WebNest Studio

Written by

WebNest Studio Engineering Team

Practical lessons from developers working with Java, Python, React, databases and production systems.

Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering