Error HandlingIntermediate8 min50 / 63

Raising & Custom Exceptions

Learn how to signal errors yourself with raise, and how to create your own custom exception types so your code fails loudly and clearly.

You have seen Python raise errors when something goes wrong — like dividing by zero or accessing a missing key. But what about errors that you want to signal? What if someone passes a negative age, or tries to withdraw more money than they have?

That is what raise is for. You can throw an error yourself, with a clear message, the moment something goes wrong. This is called raising an exception, and it is one of the most important skills in writing reliable code. The principle is called fail loudly and early: catch the problem immediately instead of letting it travel silently to some other part of your program.

See it in action

Visual walkthrough1 / 5
1

You Can Throw Errors Too

Python raises errors when it detects a problem — but you can raise them yourself. This is how you fail loudly and early, catching bad input the moment it arrives.

Better to crash with a clear message now than to silently produce wrong results later.
Think of it like

The Smoke Alarm Analogy

A smoke alarm that only goes off when the house is already on fire is not very useful. You want it to trigger the moment it detects smoke — early, loudly, clearly.

Raising exceptions is the same idea. Instead of letting your program silently do the wrong thing and explode later in a confusing way, you catch the bad situation immediately and signal it with a descriptive message.

#The raise Statement

raise stops the function immediately and sends the error up to whoever called it.
def set_age(age):
    if age < 0:
        raise ValueError("Age cannot be negative.")
    print(f"Age set to {age}")

set_age(25)
set_age(-3)

The syntax is: raise ExceptionType("your message here"). Python has many built-in exception types — pick the one that best describes the problem:

  • ValueError — the value is wrong (e.g. a negative age, an empty string where you need text)
  • TypeError — the wrong type was passed (e.g. a string where you need a number)
  • RuntimeError — something went wrong that does not fit a more specific category

The message you pass shows up in the traceback, so make it specific and human-readable.

#Custom Exception Classes

Built-in exceptions cover general problems. But sometimes your domain has specific errors that deserve their own name — like InsufficientFundsError in a banking app, or MissingIngredientError in a recipe app. You create a custom exception by making a new class that inherits from Exception. A single pass inside is all you need to get started.

class InsufficientFundsError(Exception): pass is all you need. Callers can catch it by name.
class InsufficientFundsError(Exception):
    pass

def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientFundsError(
            f"Cannot withdraw {amount}. Balance is only {balance}."
        )
    return balance - amount

try:
    withdraw(50, 200)
except InsufficientFundsError as e:
    print(f"Transaction failed: {e}")
Tip

Name Your Exceptions Well

Custom exception names should end in Error (e.g. InsufficientFundsError, not InsufficientFunds) and read like a description of what went wrong. This makes except blocks self-documenting.

A bare raise inside except re-raises the current exception. Perfect for logging before propagating.
class InsufficientFundsError(Exception):
    pass

def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientFundsError(
            f"Cannot withdraw {amount}. Balance is only {balance}."
        )
    return balance - amount

def process_payment(balance, amount):
    try:
        return withdraw(balance, amount)
    except InsufficientFundsError:
        print("[LOG] Payment attempt failed.")
        raise  # re-raises the same exception unchanged

process_payment(50, 200)
Common mistake

Don't Swallow Exceptions Silently

A very common beginner mistake:

``python try: do_something() except Exception: pass # the error disappears silently! ``

This hides bugs and makes programs nearly impossible to debug. If you catch an exception, either handle it meaningfully, log it, or re-raise it.

#Adding Extra Data to Custom Exceptions

Custom exception classes can store structured data as attributes, not just a message string.
class InsufficientFundsError(Exception):
    def __init__(self, balance, amount):
        self.balance = balance
        self.amount = amount
        super().__init__(
            f"Cannot withdraw {amount}. Balance is only {balance}."
        )

try:
    raise InsufficientFundsError(50, 200)
except InsufficientFundsError as e:
    print(e)
    print(f"You are short by: {e.amount - e.balance}")
Quick check

What does a bare raise (with no argument) do inside an except block?

Key takeaways

  • Use raise ExceptionType("message") to signal an error the moment you detect a problem.
  • Pick the right built-in type (ValueError, TypeError, etc.) or create your own by subclassing Exception.
  • Custom exceptions make your code more readable and let callers handle specific errors precisely.
  • A bare raise inside except re-raises the current exception — useful for logging before propagating.
  • Fail loudly and early: raise at the first sign of bad input rather than letting the problem spread silently.
Practice challenges
Test yourself · earn XP
0/4
Predict the output#1

What does this code print?

predict-output
def set_age(age):
    if age < 0:
        raise ValueError("Age cannot be negative.")
    print(f"Age set to {age}")

try:
    set_age(30)
    set_age(-1)
except ValueError as e:
    print(f"Error: {e}")
Fix the bug#2

This code should raise an InsufficientFundsError when amount exceeds balance, but it crashes instead. What is wrong?

fix-bug
class InsufficientFundsError(Exception):
    pass

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

try:
    withdraw(50, 200)
except InsufficientFundsError as e:
    print(f"Failed: {e}")
Fill in the blank#3

Complete the custom exception class and the raise statement so that passing a negative amount triggers the custom error.

class NegativeAmountError():
    pass

def process(amount):
    if amount < 0:
         NegativeAmountError("Amount must not be negative.")
    print(f"Processing {amount}")
Reorder the lines#4

Put these lines in the right order to define a custom exception, raise it inside a function, and catch it with a helpful message.

1
def check_temp(t):
2
        raise TooHotError("Temperature is too high!")
3
class TooHotError(Exception):
4
    if t > 100:
5
    pass
Your turn
Practice exercise

Create a custom exception called InvalidTemperatureError. Then write a function set_temperature(temp) that raises it if temp is below -273.15 (absolute zero — the coldest physically possible temperature) or above 1000000. Otherwise, print 'Temperature set to {temp} degrees.' Test it with a valid temperature, one that is too cold, and one that is too hot.

Try it live — edit the code and hit Run to execute real Python:

solution.py · editable