PRODUCTION ALERT
billing.py · calculate_bill() failing silently
B
BYTECOP
CONCEPT · SILENT ERROR SWALLOWING
New here? Start with the basics.

Let's say a circus puts up a safety net under a trapeze act.

The net is sized for one specific case: if the flyer misses their catch, they land in it safely.

That's try / except. Wrap the risky part in a try block, and if it fails the way you expected, the matching except clause catches the fall instead of the crash reaching the audience.

Now someone decides the net isn't safe enough, so they stretch it to cover the entire stage. A stagehand ten feet away trips over a cable and falls too, right into the same net.

try:
    charge_card(customer)
except Exception:
    return {"status": "declined"}
# catches the declined card, and the unrelated bug, identically

From the audience, nothing looks different. Somebody fell. Somebody landed in the net. The show goes on, and nobody notices there's now a tripping hazard on stage, because the net quietly caught the evidence along with the performer.

TRY BLOCK
The risky operation: a lookup, a network call, a calculation that might fail.
EXCEPTION RAISED
Something goes wrong inside it. Python raises an object describing what failed.
EXCEPT CLAUSE
Only exceptions matching what you name here get intercepted, everything else keeps propagating.
HANDLER RUNS
Your fallback logic executes. The caller gets whatever you return here instead of a crash.
Concept · going deeper

Silent Error Swallowing

So far, that's try/except working exactly as intended: a specific failure, handled gracefully. Silent error swallowing starts one decision later, not whether to catch a failure, but how wide a net you cast when you do.

An except clause is a filter, not a floor. It only stops what you actually name. except PaymentDeclinedError: matches that one thing (and its subclasses) and lets every other kind of failure keep propagating upward, crash and all. except Exception:, or worse, a bare except:, matches almost everything, because in Python nearly every runtime failure is some kind of Exception. The line between "defensive" and "a trapdoor for every bug in this block" is exactly how wide you make that net.

Broad catches feel responsible. Nobody wants a single edge case to crash a checkout, a login, a whole request. So a function that's supposed to handle "the card was declined" quietly grows into a function that handles "anything that goes wrong here, ever." That expansion usually isn't a decision anyone makes on purpose. It's except SomeSpecificError widened to except Exception under deadline pressure, "just to be safe," and left that way.

A broad catch can't tell your bug from the failure it was written for. Here's the part that actually bites. Once that block reads except Exception: return None, a totally unrelated defect (a typo'd dictionary key, a value that's a string instead of a number, an assumption that turned out to be wrong) raises an exception too. It gets caught by the exact same block, and comes back out looking exactly as "handled" as the case you actually designed for. Nothing at the call site can tell the difference.

The rule: a broad except doesn't handle errors. It hides them, including the ones you never expected.

The trap: code that fails by quietly succeeding. The function doesn't crash. It doesn't log anything alarming. It just returns a plausible-looking default (None, 0, an empty list, a status that reads "declined") and every caller downstream treats that default as real data, because nothing about it looks like a failure. The bug isn't that the code broke. It's that the code kept running and never told anyone it was wrong.

Narrow vs. broad except clauses under the same unrelated bug A narrow except clause lets an unrelated bug crash visibly. A broad except clause absorbs the same bug and returns the exact same output as a legitimate, expected case. Narrow Catch: Bugs Stay Visible except PaymentDeclinedError Card declined Typo bug (KeyError) try / except matches only what's named Handled: {"status": "declined"} ✕ Unhandled crash: visible immediately Broad Catch: Everything Looks The Same except Exception Card declined Typo bug (KeyError) try / except matches almost anything Handled: {"status": "declined"} one of these two is actually a bug Same output, two different causes. The except clause can't tell you which one happened.
expected, handled correctly  ·  a real bug  ·  indistinguishable once swallowed

Don't take our word for it. Flip the except clause yourself and watch what comes back.

The Except Clause Sandbox

Flip between a narrow and a broad except clause, then trigger a failure. Watch what the function actually returns.

Except clause
Trigger a failure
authorize_payment.py

      
Function Returned
What Actually Happened

Switch to the broad except clause, trigger one of the two bug scenarios, and see what comes back before continuing.

billing-serviceworkspace
Files
invoice_service.py
Test runner

Loading Python runtime…

Hints