When should you use `assert` vs raising an exception?
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 for | Use an exception for |
|---|---|
| Internal invariants ("this should be mathematically impossible") | Expected failure modes (bad user input, missing file, network error) |
| Sanity checks in tests | Validating anything from outside the program's trust boundary |
| Documenting assumptions for other developers/debugging | Anything 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.