Many developers treat try-catch as a universal safety net. But while a try block is virtually free when code runs smoothly, the moment an exception is thrown, you pay a massive performance “tax.”
My recent benchmarks show that throwing an exception can be hundreds of times slower than a simple defensive if check. Here’s why:
- The “Unhappy Path” is a Heavyweight
When an exception is thrown, the .NET runtime pauses execution and transforms into a complex debugger:- Heap Allocation: It immediately allocates a new
Exceptionobject, increasing memory and GC pressure. - The Stack Walk: The runtime must walk backward through every method in the call stack, building the
StackTracevia heavy metadata lookups. - Two-Pass Search: The CLR first scans for a matching
catch, then walks the stack a second time to executefinallyblocks before transferring control.
- Heap Allocation: It immediately allocates a new
- Defensive Coding vs. Global Safety Nets
My rule of thumb for high-performance code:- Internal Scope (Defensive): For self-contained logic (null checks, range validation), use Defensive Coding. If you can detect a problem with an
ifstatement, athrowis an architectural defect. - External Scope (Exceptional): Reserve
try-catchfor non-deterministic boundaries—resources outside your app’s control, like Database timeouts, Network blips, or File System race conditions.
- Internal Scope (Defensive): For self-contained logic (null checks, range validation), use Defensive Coding. If you can detect a problem with an
- The Bottom Line
Indiscriminate use of exceptions for control flow is a “silent killer” of throughput. Leverage the Tester-Doer or Try-Parse patterns to stay on the “Happy Path.”
Keep your logic defensive and your exceptions truly exceptional.