When should you use `assert` vs raising an exception?

5 minintermediateexceptionsassertbest-practices

Quick Answer

`assert` is for catching **programmer errors** and internal invariant violations during development/testing — it can be globally disabled with the `-O` (optimize) flag, so it must never be used to validate untrusted input or enforce business rules that need to hold in production. Raise a real exception (`ValueError`, a custom exception) for anything that represents an expected, recoverable failure mode — bad user input, a missing file, a failed network call.

Detailed Answer

The critical fact: assert can be compiled away

def withdraw(balance, amount):
    assert amount <= balance, "insufficient funds"   # DON'T rely on this for real validation!
    return balance - amount
python script.py         # assert runs normally
python -O script.py       # assert is REMOVED entirely -- the check never happens!

Running Python with -O (or setting PYTHONOPTIMIZE) strips out every assert statement from the compiled bytecode. If withdraw's only protection against overdrawing is an assert, running in optimized mode silently disables that protection — a serious bug if this were guarding something security- or correctness-critical in production.

What assert is actually for: internal invariants and dev-time checks

def calculate_average(numbers):
    result = sum(numbers) / len(numbers)
    assert result >= min(numbers), "average should never be below the minimum"
    return result

This assertion checks something that should be mathematically impossible to fail if the code is correct — it exists to catch a bug in calculate_average itself during development/testing, not to validate caller-supplied input. If it fires, it means there's a logic error in the function, not a legitimate "bad input" case a caller should handle.

What should be a real exception instead

def withdraw(balance, amount):
    if amount > balance:
        raise ValueError(f"cannot withdraw {amount}: balance is only {balance}")
    return balance - amount

This is a real, expected failure mode that callers legitimately need to handle (a user trying to overdraw), and it must be enforced regardless of whether the interpreter runs with -O — a plain if/raise always executes.

The practical rule of thumb

Use assert forUse an exception for
Internal invariants ("this should be mathematically impossible")Expected failure modes (bad user input, missing file, network error)
Sanity checks in testsValidating anything from outside the program's trust boundary
Documenting assumptions for other developers/debuggingAnything that must still be enforced in production with -O

A useful gut check: "if this assertion is removed entirely, does the program still behave correctly for all valid inputs, just without the early/loud failure on a bug?" If yes, assert is appropriate. If removing it would let bad data flow through silently, it needs to be a real exception instead.

Interview-ready summary: assert documents and checks internal invariants that should be impossible to violate if the code is correct, and it can be stripped entirely with -O — never use it to validate user input, enforce business rules, or guard anything that must hold in production. For those, raise a real exception explicitly.