What's the difference between `except Exception`, a bare `except:`, and catching specific exceptions?

5 minbeginnerexceptionsexceptbest-practices

Quick Answer

Catching a **specific exception** (`except ValueError:`) only handles that error type, letting anything unexpected propagate — the safest and most intentional option. `except Exception:` catches almost everything you'd reasonably want to handle, but not `SystemExit`/`KeyboardInterrupt`/`GeneratorExit`. A **bare `except:`** catches literally everything, including those — it's almost always a bug, since it can swallow Ctrl+C, hide `sys.exit()`, and mask programming errors like `NameError` or `AttributeError` typos.

Detailed Answer

Specific: the default, correct choice

try:
    value = int(user_input)
except ValueError:
    print("please enter a valid number")

Only ValueError (or its subclasses) is handled here — a KeyboardInterrupt during the try block, or a typo causing a NameError, propagates normally instead of being silently absorbed. This is almost always what you want: handle the error you anticipated, let anything else surface.

except Exception: — a broad but bounded net

try:
    risky_operation()
except Exception as e:
    log.error(f"operation failed: {e}")

Reasonable at a system boundary (a web request handler, a task queue worker) where you genuinely want to catch "anything that went wrong during business logic" and convert it to a controlled response/log entry, without swallowing process-control signals. Still risky if overused deep inside application logic, since it can mask bugs (a typo raising NameError gets logged and ignored instead of crashing loudly during development).

Bare except: — almost always a mistake

try:
    risky_operation()
except:            # DON'T -- catches EVERYTHING, including:
    pass             # SystemExit, KeyboardInterrupt, and typos like NameError

A bare except: is equivalent to except BaseException: — it will swallow sys.exit() (so your program refuses to actually exit), KeyboardInterrupt (so Ctrl+C appears to do nothing), and any programming error (AttributeError from a typo) that you'd want to see and fix immediately. Linters (flake8, ruff) flag bare except: for exactly this reason.

Multiple specific exceptions

try:
    process(data)
except (ValueError, TypeError) as e:
    print(f"bad input: {e}")
except KeyError as e:
    print(f"missing field: {e}")

A tuple of exception types in one except clause handles them identically; separate except clauses let you respond differently per error type. Order matters — Python checks clauses top to bottom and uses the first match, so a more specific exception type must come before a more general one that would otherwise shadow it (e.g., except ValueError before except Exception).

The practical rule

Catch the most specific exception type that you know how to handle, as close as possible to where you can meaningfully react to it. Reserve except Exception for true boundary layers (top-level request handlers, background job runners), and never use a bare except: — if you truly need to catch everything including SystemExit/KeyboardInterrupt (rare — e.g., a supervisor process), spell out except BaseException: explicitly so the intent is unambiguous.

Interview-ready summary: Prefer catching the specific exception type you can actually handle. except Exception is acceptable at application boundaries as a last line of defense, but never use a bare except: — it silently swallows SystemExit/KeyboardInterrupt and masks genuine programming errors that should crash loudly instead.