Error Handling Workflow
When to Use try / except
A short, durable guide to deciding whether a given failure deserves a try/except — and, if so, what the except block should actually do.
TL;DR
- Catch an error only when you can recover from it. No recovery → let it raise; the traceback is the signal you want.
- Catch the specific error you expect so the
exceptstays narrow. Per Real Python, it's best to "catch specific exceptions to avoid masking unexpected errors." Reserve catch-alls for a single, deliberate top-level boundary. - Knowing the error is necessary but not sufficient — you also need a defined response.
"Try-except is intended to be used in cases where you know a particular error will happen and you know what you should do if that error happens." — Reuben
The Two-Question Test
Before writing try/except, both answers must be yes:
- Do I know why/when this fails? (transient network blip, missing key, bad input)
- Can — and should — the program continue if it does? (skip, fall back, retry, clean up)
If #2 is no, don't catch it.
Prefer checking upfront when you can. If a cheap precondition check (membership, existence) avoids the error entirely, do that (LBYL) instead of catching. Reach for try/except (EAFP) when you can't pre-check or a check would race — and note that "exception handling is fast and efficient in Python" (Real Python), so EAFP isn't a performance compromise.
The Patterns
A. When catching is right — three shapes of recovery
A1 · Optional step (nice-to-have). The failed operation wasn't required; downstream runs unchanged. Catch, log, continue.
try:
enrich(record) # optional
except KnownError as err:
log.warning(err) # downstream doesn't depend on it
# ...continue normally
A2 · Fallback required. Downstream does depend on the result, so the except must establish a recovery state — a default, an alternate branch, a flag — before continuing. This include extra clean-up that may be required if a function fails.
try:
value = fetch()
except KnownError:
value = DEFAULT # establish recovery state
mark_degraded() # downstream adapts to it
A3 · Augment, then re-raise. You catch only to enrich the failure — capture diagnostics, attach context, fire an alert — then re-raise so it still fails loudly. The except improves observability; it does not swallow.
try:
risky_step()
except KnownError:
capture_diagnostics() # screenshot, context, alert
raise # still surfaces
Logging tip: inside a handler,logger.exception(...)"captures the full stack trace in the context of the except block" (Loggly) — so prefer it overlog.error(str(err)).
B. When you should not write your own try/except
B1 · The library already handles it. Transient network/API failures (timeouts, 5xx, rate limits) are dealt with by configuring the client's retry/backoff — not by hand-rolling a catch. Most HTTP clients and API SDKs retry internally; don't double-wrap them.
client = Client(retries=Retry(total=3, backoff_factor=0.3,
status_forcelist=[429, 500, 502, 503, 504]))
client.call() # no hand-rolled try/except; 4xx still fails fast
Quick Reference
| Situation | try/except? |
Why / pattern |
|---|---|---|
| Precondition is cheaply checkable | No — check first (LBYL) | Avoid the error; no exception machinery |
| Optional step, downstream unaffected | Yes — catch, log, continue | A1 |
| Result needed, but a fallback exists | Yes — catch, set fallback, continue | A2 |
| Need richer diagnostics on failure | Yes — catch, enrich, re-raise | A3 |
| Transient network / API error | No (your own) — configure library retries | B1 |
| Module import / fully-controlled inputs | No | Not recoverable; traceback is the signal |
| Cleanup that must always run | Use finally |
A2 |
References
- Real Python — Python Exceptions
- Real Python — LBYL vs EAFP
- urllib3 — Retry
- Loggly — Logging exceptions in Python
- AWS CodeGuru — Swallowed exceptions anti-pattern
- Origin: internal engineering discussion, June 10, 2026