Code that uses try/catch for conditions the caller could check directly — `try { amounts[i] / 100 } catch { return 0 }` where checking bounds is mechanical.
Exceptions used for predictable, checkable conditions become an explicit precheck the caller can perform, leaving exceptions for truly exceptional cases.
Before the refactoring
try {return amounts[i] / 100;} catch (e) {return 0;}
After the refactoring
if (i >= amounts.length) return 0;return amounts[i] / 100;
Exception flow obscures the rule; debuggers stop on benign throws; reasoning about the happy path requires reading the catch handler.
Race conditions: the precheck may pass and the operation still fail (TOCTOU). Use prechecks only for conditions the caller can verify without a race.
The error path is local and visible; reading code top-to-bottom describes the rules rather than the failure response; debuggers stop catching benign throws.
Replacing every exception with a precheck — including ones for conditions the caller can't atomically verify (TOCTOU race windows).