Overhead of try/finally in C?
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.
Introduction
The try/finally construct in C# guarantees that cleanup code in the finally block executes regardless of whether an exception is thrown. Developers sometimes worry about the performance cost of wrapping code in try/finally, especially in hot loops or performance-critical paths. In practice, the .NET JIT compiler applies optimizations that make the overhead negligible when no exception is thrown.
How try/finally Works at the JIT Level
When the JIT compiler encounters a try/finally block, it generates metadata that maps the protected region of code to the corresponding finally handler. During normal execution, the CPU runs through the try block instructions without any additional branching or checking. The runtime only consults the exception-handling tables when an exception actually occurs.
In the happy path where no exception is thrown, the generated machine code for the try block is nearly identical to what it would be without the try/finally wrapper. The JIT still inlines methods, eliminates bounds checks, and applies other optimizations within the try block.
Measuring the Overhead with BenchmarkDotNet
You can measure the actual overhead using BenchmarkDotNet to compare a method with and without try/finally.
In typical results, the two methods produce nearly identical timings. The difference is within the margin of measurement noise, confirming that try/finally with no exception has effectively zero runtime cost.
Where try/finally Does Have Cost
The overhead becomes real in two scenarios. First, when an exception is actually thrown, the runtime must walk the stack, find the matching handler, and execute the finally block. This process is orders of magnitude slower than normal execution.
Second, the presence of a try/finally block can inhibit certain JIT optimizations. For example, the JIT may not inline a method that contains exception-handling constructs, and it may limit register allocation optimizations within the protected region.
In modern .NET (6 and later), the JIT has improved significantly and can inline methods with simple try/finally blocks in many cases.
try/finally vs the using Statement
The using statement in C# compiles down to a try/finally block. They have identical performance characteristics.
The using declaration syntax introduced in C# 8 also generates the same try/finally pattern, with the finally block at the end of the enclosing scope.
Since using is just syntactic sugar over try/finally, there is no reason to avoid using for performance reasons.
When the Overhead Matters
In the vast majority of applications, the overhead of try/finally is not measurable. However, there are narrow scenarios where you should be aware of it.
In micro-benchmarks running billions of iterations, placing try/finally inside a tight loop rather than outside it can show a small but consistent difference because it may affect the JIT's ability to optimize the loop body. The solution is to structure your code so that try/finally wraps the loop rather than being inside it.
Common Pitfalls
- Avoiding try/finally for performance reasons: The overhead on the happy path is effectively zero. Skipping
try/finallyto save performance leads to resource leaks that cause far worse problems than any micro-optimization could solve. - Throwing exceptions in normal control flow: The real cost of exception handling comes from throwing and catching exceptions, not from the
try/finallystructure itself. Never use exceptions for expected control flow like loop termination or validation. - Placing try/finally inside tight loops: While the overhead is small, placing a
try/finallyinside a loop that runs millions of times can prevent loop optimizations. Move thetry/finallyoutside the loop when possible. - Assuming using is slower than manual cleanup: The
usingstatement compiles to the sametry/finallypattern. ManualDispose()calls withoutusingrisk missing cleanup on exceptions and offer no performance benefit. - Ignoring JIT version differences: Older .NET Framework JIT compilers are more conservative about optimizing around
try/finallythan modern .NET JIT. If you are targeting .NET Framework 4.x, test performance-critical paths with benchmarks rather than assuming modern JIT behavior.
Summary
- The
try/finallyconstruct has effectively zero overhead when no exception is thrown because the JIT generates normal code with exception-handler metadata consulted only on exceptions. - The real cost comes from actually throwing exceptions, which involves stack walking and handler lookup.
- The
usingstatement compiles totry/finallyand has identical performance characteristics. - Place
try/finallyoutside tight loops rather than inside them to avoid inhibiting loop optimizations. - Always prefer correct resource cleanup with
try/finallyorusingover micro-optimizations that risk resource leaks.
Related reading
- P versus NP Clarification
- Packing arbitrary triangles into a finite box?
- Packing irregular circles on the surface of a sphere
- Paging Resultsets in Cassandra with compound primary keys - Missing out on rows
- Override Authorize Attribute in ASP.NET MVC
- Overriding GetHashCode
- Overriding method with selector ''touchesBeganwithEvent'' has incompatible type ''NSSet, UIEvent - ''
- Package is not found in the following primary source

DSA Fundamentals
Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.