How do you define and raise custom exceptions in Python?
Subclass Exception (not BaseException) to create a custom exception. Give it a clear name, optionally store structured data in __init__, and place it in a dedicated exceptions module. Raise it with raise and catch it with except, narrowing to your specific type before broader ones.
How to think about it
Custom exceptions are about communication. A ValueError tells the caller almost nothing; a SchemaValidationError with a .field attribute tells them exactly what went wrong and gives them something to programmatically act on. The question is really testing whether you design exceptions like an API — clear names, structured data, sensible hierarchy.
What’s really being tested
Interviewers want to see that you subclass Exception (not BaseException), that you know how to attach structured data, and that you understand exception chaining with from e to preserve context.
Step 1 — The minimal custom exception
A bare subclass with a docstring is sufficient for most cases. The class name itself carries the meaning.
Step 2 — Add structured data
Pass metadata as instance attributes instead of stuffing everything into the message string. Callers can then branch on e.code without parsing text — far more robust.
Step 3 — Build a hierarchy for a library
A base exception for your package lets callers catch all your errors with one except, while still allowing fine-grained handling when needed.
Step 4 — Chain exceptions to preserve context
When you catch one exception and raise another, use raise NewError(...) from original to set __cause__. The traceback shows both, which is invaluable for debugging.
The key insight — exceptions are part of your API
When you write a library or service, the exceptions you raise are part of the contract you make with your callers. They should be as carefully designed as your function signatures: named precisely, carrying the data a caller needs to recover or log intelligently, and arranged in a hierarchy that matches the domain.