Examples of Raising Exceptions in Python: 3 Practical Patterns You’ll Actually Use

If you write Python for anything beyond toy scripts, you will eventually need clear, intentional error handling. That’s where raising exceptions comes in. In this guide, we’ll walk through real examples of raising exceptions in Python: 3 practical examples that mirror the way production code is written today. Instead of abstract theory, we’ll focus on situations you actually hit in day‑to‑day development: validating user input, enforcing API contracts, and guarding critical business rules. We’ll also look at how modern teams in 2024–2025 structure their exception hierarchies, how they log and surface errors, and why explicit `raise` statements often make your code easier to debug than silent failures. Along the way, you’ll see several concrete examples of how to raise built‑in exceptions, when to define your own, and how to attach helpful context so the next person reading the traceback (which might be you, three months from now) can fix the problem fast.
Written by
Jamie
Published

Let’s start with three core patterns you’ll see over and over again. These are the best examples of raising exceptions in Python because they map directly to how real applications behave.

The three practical patterns:

  • Validating function arguments
  • Guarding I/O and external resources
  • Enforcing business rules and domain logic

Each of these patterns shows an example of raising exceptions in Python in a way that’s explicit, readable, and easy to debug.


Pattern 1: Validating function arguments (bad input should explode early)

The first and most common example of raising exceptions in Python is simple argument validation. Instead of letting bad data leak deeper into your code, you stop it at the boundary.

def calculate_discount(price: float, percent: float) -> float:
    if price < 0:
        raise ValueError(f"price must be non-negative, got {price}")

    if not (0 <= percent <= 100):
        raise ValueError(f"percent must be between 0 and 100, got {percent}")

    return price * (1 - percent / 100)

Here you see one of the clearest examples of raising exceptions in Python: use ValueError when the value of an argument is invalid but the type is fine. This pattern is everywhere in well‑written libraries.

A more realistic API-style example

Imagine you’re building a small internal API to create user accounts. You want to enforce that the email looks reasonable and that age is at least 13.

import re

EMAIL_RE = re.compile(r"^[^@]+@[^@]+\.[^@]+$")

class ValidationError(ValueError):
    """Raised when user input fails validation."""


def create_user(username: str, email: str, age: int) -> dict:
    if not username or len(username) < 3:
        raise ValidationError("username must be at least 3 characters long")

    if not EMAIL_RE.match(email):
        raise ValidationError(f"invalid email address: {email}")

    if age < 13:
        raise ValidationError("user must be at least 13 years old")

#    # pretend we save to a database here
    return {"username": username, "email": email, "age": age}

This is a clean example of raising exceptions in Python using a custom ValidationError that still fits into the ValueError family. When your API layer catches ValidationError, it can return a 400 Bad Request with a helpful message.

Why this matters in 2024–2025

Modern Python stacks—FastAPI, Django REST Framework, Pydantic—lean heavily on validation and typed data models. Pydantic, for instance, uses exceptions internally to signal validation failures and then turns them into structured error responses.

  • FastAPI docs on error handling: https://fastapi.tiangolo.com/tutorial/handling-errors/
  • Pydantic documentation: https://docs.pydantic.dev/

You don’t need to memorize every built‑in exception type, but having a few go‑to examples of raising exceptions in Python for input validation will make your APIs and CLIs far more predictable.


Pattern 2: Guarding I/O and external resources with explicit raise

The second of our 3 practical examples focuses on I/O: files, network calls, and external services. When something outside your process fails, you often want to catch the low‑level error, wrap it, and raise a higher‑level exception that makes sense to your application.

Wrapping low-level file errors

from pathlib import Path

class ConfigError(RuntimeError):
    """Raised when the application configuration is invalid or missing."""


def load_config(path: str) -> str:
    config_path = Path(path)

    if not config_path.exists():
        raise ConfigError(f"config file not found: {config_path}")

    try:
        return config_path.read_text(encoding="utf-8")
    except OSError as exc:
#        # Wrap the original exception with more context
        raise ConfigError(f"failed to read config file: {config_path}") from exc

Notice the from exc syntax. This is one of the best examples of raising exceptions in Python while preserving the original traceback. You get a domain‑specific ConfigError, but you don’t lose the underlying OSError details.

Network call with retries and a custom exception

In 2024–2025, a lot of Python code is glue code: calling APIs, talking to cloud services, orchestrating data pipelines. You often want a clear signal when an external dependency fails.

import time
import requests

class ExternalServiceError(RuntimeError):
    """Raised when a required external service cannot be reached."""


def fetch_user_profile(user_id: str, retries: int = 3) -> dict:
    url = f"https://api.example.com/users/{user_id}"

    for attempt in range(1, retries + 1):
        try:
            response = requests.get(url, timeout=3)
            response.raise_for_status()
            return response.json()
        except (requests.Timeout, requests.ConnectionError) as exc:
            if attempt == retries:
                raise ExternalServiceError(
                    f"failed to reach user service after {retries} attempts"
                ) from exc
            time.sleep(0.5 * attempt)

Here you see another example of raising exceptions in Python: translate low‑level network errors into a single ExternalServiceError that your higher‑level code can handle in one place.


Pattern 3: Enforcing business rules with domain-specific exceptions

The third of our 3 practical examples is where Python exceptions really shine: encoding business rules. Instead of returning magic values like None or -1, you raise a clear exception when a rule is violated.

Example: banking-style transfer with rule enforcement

class InsufficientFundsError(RuntimeError):
    pass

class DailyLimitExceededError(RuntimeError):
    pass


def transfer_funds(balance: float, amount: float, daily_total: float, daily_limit: float) -> float:
    if amount <= 0:
        raise ValueError("transfer amount must be positive")

    if amount > balance:
        raise InsufficientFundsError(
            f"insufficient funds: balance={balance}, attempted={amount}"
        )

    if daily_total + amount > daily_limit:
        raise DailyLimitExceededError(
            f"daily limit {daily_limit} exceeded by transfer of {amount}"
        )

    return balance - amount

This single function gives several useful examples of raising exceptions in Python:

  • ValueError for an obviously invalid amount
  • InsufficientFundsError for a financial rule violation
  • DailyLimitExceededError for a compliance or risk rule

Your application layer can now catch these exceptions separately and show different messages, log at different severity levels, or trigger alerts for suspicious activity.

Example: feature flags and unsupported operations

Another real‑world example of raising exceptions in Python is when a feature isn’t available under certain conditions—think pricing tiers, region restrictions, or experimental flags.

class FeatureNotAvailableError(PermissionError):
    pass


def export_report(user_plan: str, format: str) -> bytes:
    if format not in {"pdf", "csv"}:
        raise ValueError(f"unsupported export format: {format}")

    if user_plan == "free" and format == "pdf":
        raise FeatureNotAvailableError(
            "PDF export is available only on paid plans"
        )

#    # ... generate and return bytes here ...
    return b"fake-data"

This pattern makes it obvious why something failed, rather than leaving your support team guessing.


More real examples of raising exceptions in Python (beyond the core 3)

So far we’ve covered the main 3 practical patterns. To make this guide more concrete, let’s add a few extra real examples you’ll actually copy‑paste into your own code.

Example: type checking with TypeError

def send_email(to: list[str] | str, subject: str, body: str) -> None:
    if isinstance(to, str):
        to = [to]

    if not isinstance(to, list):
        raise TypeError("to must be a string or list of strings")

    if not all(isinstance(addr, str) for addr in to):
        raise TypeError("all recipients must be strings")

#    # send the email here

This is a straightforward example of raising exceptions in Python when the type of an argument is wrong.

Example: defensive programming with NotImplementedError

class Shape:
    def area(self) -> float:
        raise NotImplementedError("subclasses must implement area()")


class Circle(Shape):
    def __init__(self, radius: float) -> None:
        self.radius = radius

    def area(self) -> float:
        return 3.14159 * self.radius ** 2

Raising NotImplementedError is a classic example of how to signal abstract behavior in Python’s object‑oriented code.

Example: data pipelines and sanity checks

In data engineering and analytics, silent data corruption is a nightmare. Many teams now build in aggressive sanity checks that raise exceptions when distributions or ranges look wrong. For background on why this matters, see the discussion of data quality in the U.S. National Institute of Standards and Technology (NIST) guidelines: https://www.nist.gov/itl

class DataQualityError(RuntimeError):
    pass


def validate_temperature_readings(readings_f: list[float]) -> None:
    if not readings_f:
        raise DataQualityError("no temperature readings provided")

    if any(t < -100 or t > 200 for t in readings_f):
        raise DataQualityError("temperature readings out of expected range")

Again, this is a very practical example of raising exceptions in Python to protect downstream models and dashboards.


How to design your own exception hierarchy (and why it matters)

Once you’ve seen enough examples of raising exceptions in Python, a pattern emerges: the best codebases don’t just throw random built‑ins everywhere. They define a small, organized hierarchy of custom exceptions.

A simple pattern that works well in 2024–2025 microservices and monoliths alike:

class AppError(Exception):
    """Base class for all application-specific errors."""


class ValidationError(AppError):
    pass


class NotFoundError(AppError):
    pass


class PermissionDeniedError(AppError):
    pass

Then, everywhere in your code, you raise these domain‑specific types:

def get_order(order_id: str) -> dict:
    order = _load_order_from_db(order_id)
    if order is None:
        raise NotFoundError(f"order {order_id} does not exist")
    return order

Your API boundary can now do something like:

try:
    order = get_order(order_id)
except NotFoundError as exc:
    return {"error": str(exc)}, 404
except ValidationError as exc:
    return {"error": str(exc)}, 400
except AppError as exc:
    return {"error": "internal error"}, 500

This pattern builds on all the examples of raising exceptions in Python we’ve seen so far and gives you a clean, maintainable way to handle errors across a growing codebase.

For a deeper treatment of API error design (language‑agnostic but very relevant), the U.S. Digital Service provides practical guidance on building resilient services: https://playbook.cio.gov/


Logging, observability, and exceptions in modern Python apps

In 2024–2025, exceptions are not just for local debugging. They’re signals that feed into logging, tracing, and alerting systems.

A few practical tips that fit well with the examples of raising exceptions in Python above:

  • Attach context in the message: user IDs, order IDs, config paths
  • Use raise ... from exc when you’re wrapping a lower‑level error
  • Avoid catching Exception unless you re‑raise or log with full traceback

Example with logging:

import logging

logger = logging.getLogger(__name__)


def process_payment(order_id: str, amount: float) -> None:
    try:
        _charge_card(order_id, amount)
    except ExternalServiceError as exc:
        logger.error("payment failed for order %s: %s", order_id, exc)
        raise

The point is not to hide exceptions but to raise them intentionally and consistently, then observe them with real tooling.

For general background on software reliability and error handling practices, the Software Engineering Institute at Carnegie Mellon University has accessible resources: https://resources.sei.cmu.edu/


FAQs about raising exceptions in Python

What are some real examples of raising exceptions in Python in everyday code?

Common real‑world examples include:

  • Validating user input in APIs or CLIs and raising ValueError or ValidationError
  • Wrapping file I/O failures in a ConfigError or AppError
  • Signaling business rule violations like InsufficientFundsError
  • Guarding against unsupported operations with NotImplementedError or custom types

All of these mirror the examples of raising exceptions in Python shown in the patterns above.

When should I define a custom exception instead of using a built-in one?

Define a custom exception when callers need to distinguish this error from others in a clean way. If you expect higher‑level code to say “handle all validation errors here” or “retry only on external service failures,” a custom exception type is worth it. If it’s a one‑off error that will just bubble up and crash, a built‑in like ValueError or TypeError is usually fine.

Is it bad practice to raise generic Exception?

Usually yes. Raising Exception makes it harder for callers to know what went wrong and how to recover. It’s better to raise a more specific built‑in or one of your own custom types. The best examples of raising exceptions in Python avoid Exception in favor of semantically meaningful classes.

Can I re-raise an exception with more context?

Yes, and you should. Catch the original exception, add context, and use raise NewError(...) from exc. This preserves the original traceback while giving you a higher‑level explanation. You saw this pattern in the config and network examples above.

Is using exceptions for control flow always wrong?

It depends. Using exceptions for normal control flow (like breaking out of a loop every few iterations) is usually a bad idea. Using them to signal exceptional situations—invalid input, failed I/O, violated business rules—is exactly what they’re for. The examples of raising exceptions in Python throughout this article all fall into that second category.

Explore More Exception Handling

Discover more examples and insights in this category.

View All Exception Handling