What is EAFP vs LBYL, and why does Python favor EAFP?
Quick Answer
**LBYL** ("Look Before You Leap") checks preconditions before acting (`if key in d: value = d[key]`); **EAFP** ("Easier to Ask Forgiveness than Permission") just attempts the operation and handles the exception if it fails (`try: value = d[key] except KeyError:`). Python idiomatically favors EAFP because it avoids a race condition between the check and the action (time-of-check to time-of-use, or TOCTOU), and Python's exception handling is cheap enough that it's not a performance concern for the common success case.
Detailed Answer
LBYL: check first, then act
if "key" in d:
value = d["key"]
else:
value = default
if hasattr(obj, "process"):
obj.process()
if os.path.exists(path):
f = open(path)
Each of these checks a condition, then performs the action — two separate operations, with a gap in between where the condition could theoretically become stale.
EAFP: try it, handle the failure
try:
value = d["key"]
except KeyError:
value = default
try:
obj.process()
except AttributeError:
pass
try:
f = open(path)
except FileNotFoundError:
...
Each of these makes one attempt and reacts to failure — no separate "check" step at all.
Why EAFP is preferred: avoiding TOCTOU races
# LBYL -- racy: the file could be deleted between the check and the open()
if os.path.exists(path):
f = open(path) # could still raise FileNotFoundError anyway!
# EAFP -- no race: a single atomic attempt, with a clear failure path
try:
f = open(path)
except FileNotFoundError:
...
Between os.path.exists(path) returning True and the subsequent
open(path) call, another process (or thread) could delete the file —
the LBYL version can still fail even after its check passed, so you end
up needing the try/except anyway. EAFP skips the redundant, racy check
entirely and handles the one real failure point directly.
Why it's not a performance tradeoff in the common case
A try block costs essentially nothing when no exception is raised —
the overhead only shows up when an exception actually occurs. So for
operations that usually succeed (the common case for dict lookups,
attribute access, file opens), EAFP is both cleaner and has no
meaningful performance downside; it only becomes more expensive than
LBYL in the (usually rare) case where failures are frequent enough that
exception-raising overhead adds up.
When LBYL still makes sense
if amount > 0: # a business-logic precondition, not error handling
withdraw(amount)
LBYL remains appropriate for validating business rules/preconditions that aren't really "errors" in the exceptional sense (e.g., checking a withdrawal amount is positive) — EAFP is specifically about operations whose failure mode is naturally expressed as an exception (missing key, missing attribute, missing file), not a stand-in for all conditional logic.
Interview-ready summary: EAFP attempts an operation and handles
failure via except; LBYL checks preconditions first. Python favors
EAFP for operations with a natural exception-based failure mode because
it avoids check-then-act race conditions and costs nothing extra when
the operation succeeds, which is the common case.