Learn How to Use Exception Handling in Python: Absolute Beginners Tutorial

Every Python program eventually encounters something unexpected — a file that does not exist, a number that cannot be divided, or input that is not what the code expected. Exception handling gives you a structured way to respond to those situations without the program crashing.

Python separates two categories of problems: syntax errors, which are caught before your code ever runs, and exceptions, which occur during execution. This tutorial focuses entirely on exceptions — what they are, how to catch them, and how to raise them yourself when your own code needs to signal a problem.

What Is an Exception?

An exception is an event that interrupts the normal flow of a program. When Python cannot complete an operation — for example, dividing by zero or opening a file that does not exist — it creates an exception object describing what went wrong and stops executing the current block of code.

If nothing handles the exception, Python terminates the program and prints a traceback. A traceback tells you the exception type, a message explaining the problem, and the exact line where execution stopped. Reading tracebacks carefully is one of the first practical skills to develop as a Python programmer.

python
# This line raises a ZeroDivisionError
result = 10 / 0

# Output:
# Traceback (most recent call last):
#   File "example.py", line 2, in <module>
#     result = 10 / 0
# ZeroDivisionError: division by zero

The traceback shows the exception type on the last line — ZeroDivisionError — followed by a colon and a human-readable message. The lines above it show the call chain that led to the error.

Note

In Python, exceptions are objects. Every built-in exception inherits from the base class BaseException. The exceptions you will handle day-to-day — ValueError, TypeError, FileNotFoundError, and others — all inherit from Exception, which itself inherits from BaseException.

The try and except Blocks

The try block marks code that might raise an exception. The except block defines what should happen if it does. Python attempts to run the code inside try. If an exception is raised, Python jumps immediately to the matching except clause and runs that instead.

python
try:
    number = int(input("Enter a number: "))
    print(10 / number)
except ZeroDivisionError:
    print("You cannot divide by zero.")
except ValueError:
    print("That was not a valid number.")

In the example above, two different exceptions can occur. If the user enters 0, Python raises ZeroDivisionError. If the user enters something that cannot be converted to an integer — like hello — Python raises ValueError. Each except clause handles one specific type.

Pro Tip

You can capture the exception object itself by writing except ValueError as e:. The variable e holds the exception instance, and str(e) gives you the error message. This is useful for logging exactly what went wrong.

Catching Multiple Exception Types at Once

If two exception types should trigger the same response, you can group them in a tuple inside a single except clause rather than repeating yourself.

python
try:
    value = int(user_input) / divisor
except (ValueError, ZeroDivisionError) as e:
    print(f"Input problem: {e}")
code builder click a token to place it

Build a try-except block that catches a ValueError when converting user input to an integer:

your code will appear here...
except finally: try: print("invalid input") TypeError: ValueError: int(user_input)
Why: The correct order is try: to open the guarded block, then int(user_input) as the risky operation (indented), then except followed by ValueError: (the specific type), then the indented handler. finally: is a different clause — it is not used here. TypeError: handles wrong types, not bad string-to-int conversions.

Common Built-in Exception Types

Python ships with dozens of built-in exception types. Knowing the ones you will encounter regularly makes it much easier to write targeted except clauses. The table below covers the exceptions absolute beginners run into the most.

When raised
The argument is the right type but an unacceptable value — for example, int("hello") or math.sqrt(-1).
Common cause
Converting user input that does not match the expected format.
When raised
An operation is applied to an object of the wrong type — for example, "2" + 2 or calling a non-callable object.
Common cause
Mixing incompatible types in arithmetic or function calls without explicit conversion.
When raised
The divisor in a division or modulo operation is zero — x / 0 or x % 0.
Common cause
Dynamic divisors calculated from user input or data that may be zero.
When raised
A sequence index is out of range — for example, accessing my_list[10] when the list only has five elements.
Common cause
Off-by-one errors in loops, or assuming a list has at least a certain number of items.
When raised
A dictionary lookup uses a key that does not exist in the dictionary — my_dict["missing"].
Common cause
Accessing dictionary keys derived from external data without first checking whether the key is present.
When raised
A file-open or file-access operation targets a path that does not exist — open("missing.txt").
Common cause
Hardcoded file paths, typos in filenames, or files that may have been moved or deleted at runtime.
When raised
An attribute reference or assignment fails because the object does not have that attribute — None.upper().
Common cause
Functions that can return None when the result is used without checking, or typos in method names.
spot the bug click the line that contains the bug

This function tries to safely convert a string to an integer. One line contains a bug. Click it, then hit check.

1 def safe_int(value):
2 try:
3 return int(value)
4 except TypeError:
5 return None
The fix: Change except TypeError: to except ValueError:. When int() receives a string that cannot be parsed — like "hello" — it raises a ValueError, not a TypeError. A TypeError would be raised if you passed in an incompatible type entirely (such as a list), which is a different situation. Catching the wrong exception type means the real error goes unhandled.

The else and finally Clauses

Two optional clauses extend what you can do with a try-except block: else and finally. Neither is required, but each serves a distinct purpose.

The else Clause

The else block runs only when the try block completes without raising any exception. This lets you keep the success-path logic clearly separate from both the risky operation and the error-handling code.

python
try:
    f = open("data.txt", "r")
except FileNotFoundError:
    print("File not found.")
else:
    # Runs only if open() succeeded
    content = f.read()
    print(f"Read {len(content)} characters.")
    f.close()

The finally Clause

The finally block always runs — whether the try block succeeded, raised a caught exception, or raised an uncaught one. It is the right place for cleanup code that must execute no matter what happens, such as closing a file handle or releasing a network connection.

python
try:
    result = 10 / int(input("Divisor: "))
except (ValueError, ZeroDivisionError) as e:
    print(f"Error: {e}")
else:
    print(f"Result: {result}")
finally:
    print("Calculation attempt complete.")
Watch Out

Avoid using a bare except: clause with no exception type specified. It catches everything, including SystemExit and KeyboardInterrupt, which makes the program very hard to stop and can mask real bugs. Always name at least one specific exception type.

Using raise to Trigger Exceptions Intentionally

You are not limited to catching exceptions that Python raises automatically. The raise keyword lets you signal that something has gone wrong in your own code — for example, when a function receives an argument that passes type checks but violates a business rule.

python
def set_age(age):
    if not isinstance(age, int):
        raise TypeError("age must be an integer")
    if age < 0:
        raise ValueError("age cannot be negative")
    return age

try:
    set_age(-5)
except ValueError as e:
    print(f"Invalid age: {e}")

Inside an except block you can also use raise with no arguments to re-raise the current exception after logging it or doing partial cleanup, without swallowing it.

"Errors should never pass silently. Unless explicitly silenced." — The Zen of Python, Tim Peters
Exception handling execution flow — how Python routes through try, except, else, and finally.

How to Use Exception Handling in Python

The five steps below walk through the complete structure of a well-formed exception handler, from wrapping risky code to releasing resources in finally.

  1. Wrap risky code in a try block

    Place only the code that might raise an exception inside the try block. Keep this block as small and focused as possible — wrapping large amounts of code makes it harder to know which line actually caused a problem.

  2. Catch the exception with a named except clause

    Write except SpecificExceptionType as e: to handle a known error. Using as e lets you inspect the error message with str(e) or log it. Always name the exception type — avoid bare except: which catches everything including system signals.

  3. Add an else block for code that should run only on success

    Place logic that depends on the try block having succeeded in the else clause. This keeps your success path visually distinct from error-handling code and avoids accidentally catching exceptions raised inside that success logic.

  4. Use finally for guaranteed cleanup

    Put resource-release code — closing files, network connections, or locks — in a finally block. It runs in every scenario: after a successful try, after a caught exception, and even after an uncaught exception before the program unwinds.

  5. Raise exceptions intentionally when your code detects invalid state

    Use raise ValueError("descriptive message") or another appropriate exception type when your function receives bad input. This tells callers exactly what went wrong and gives them the opportunity to handle it with their own try-except block.

Python Exception Handling Summary Points

  1. The try block contains code that might fail. Python executes it normally unless an exception is raised, in which case it jumps immediately to the matching except clause.
  2. Always name the exception type in an except clause. Catching broad exceptions hides bugs; catching specific types like ValueError or FileNotFoundError keeps the handler focused on the problem it was designed for.
  3. The else clause runs only when the try block completes without error. The finally clause always runs, making it the right place for cleanup that must not be skipped under any circumstances.
  4. You can raise exceptions deliberately with the raise keyword. Inside an except block, a bare raise re-raises the current exception, which is useful when you want to log an error without silently swallowing it.
  5. Knowing the common built-in exception types — ValueError, TypeError, ZeroDivisionError, IndexError, KeyError, FileNotFoundError, and AttributeError — lets you write targeted handlers rather than guessing which exception to catch.

Exception handling is not just about preventing crashes. It is about writing code that communicates what went wrong and gives the program a clear path to recover or fail gracefully. Start with the simplest try-except structure, name your exception types, and keep the guarded blocks small.

check your understanding question 1 of 5

Frequently Asked Questions

Exception handling in Python is a way to respond to runtime errors without crashing your program. When Python encounters an error it cannot resolve automatically, it raises an exception. You can catch that exception using a try-except block and define what should happen instead of a crash.

The try block contains code that might raise an exception. The except block runs if an exception is raised. The else block runs only if no exception was raised in the try block. The finally block always runs regardless of whether an exception occurred, making it useful for cleanup tasks like closing files.

Name the exception type after the except keyword — for example, except ValueError:. You can catch multiple types in one clause by listing them in a tuple: except (ValueError, TypeError):. Catching specific types is better practice than using a bare except: clause.

The raise keyword lets you trigger an exception intentionally. You can raise a built-in exception such as raise ValueError("age must be positive") or re-raise the current exception inside an except block by writing just raise with no arguments.

A ValueError is raised when a function receives an argument of the right type but an inappropriate value. For example, int('hello') raises a ValueError because the string 'hello' cannot be converted to an integer even though a string is a valid type for that argument.

A TypeError is raised when an operation is applied to an object of an unsuitable type. For example, adding a string to an integer like '2' + 2 raises a TypeError because Python cannot implicitly convert between those types.

Yes. A single try block can have multiple except clauses, each handling a different exception type. Python checks them in order from top to bottom and runs the first one that matches. Only one except block executes per exception.

The finally block is used for cleanup code that must run no matter what. Common uses include closing file handles, releasing network connections, and releasing locks. It runs whether the try block succeeded, raised a caught exception, or raised an uncaught exception.

If an exception is not caught by any except block, it propagates up the call stack. If it reaches the top level without being handled, Python terminates the program and prints a traceback showing the exception type, message, and the line where it occurred.

A bare except: clause catches every exception including SystemExit and KeyboardInterrupt, which can make programs difficult to stop and hide genuine bugs. It is better practice to catch specific exception types or at minimum use except Exception: which excludes system-level exceptions.