---
title: 'Why ''Just Use Exceptions'' Is Bad Advice in .NET'
source: 'https://youtube.com/watch?v=Zw0VtT7AVmQ'
video_id: 'Zw0VtT7AVmQ'
date: 2026-08-08
duration_sec: 779
---

# Why 'Just Use Exceptions' Is Bad Advice in .NET

> Source: [Why 'Just Use Exceptions' Is Bad Advice in .NET](https://youtube.com/watch?v=Zw0VtT7AVmQ)

## Summary

The video discusses the controversial topic of using the result pattern instead of exceptions for flow control in .NET applications. The presenter refutes common criticisms and demonstrates a practical refactoring example, showing how results can make code more explicit and maintainable.

### Key Points

- **Viral Result Pattern Post** [00:17] — A simple result class with success/failure flag, error code/description, and helper methods went viral with over 150k views.
- **Exceptions as Anti-Pattern** [02:55] — Using exceptions for flow control is an anti-pattern; exceptions act like goto statements, jumping to global handlers.
- **Stack Trace Without Exceptions** [04:19] — You can capture a stack trace using Environment.StackTrace without throwing an exception.
- **Refactoring to Results** [05:01] — Refactoring involves replacing expected exceptions with failure results, and defining a static class to document possible errors.
- **Honest API Design** [06:07] — The result pattern makes APIs more honest by explicitly stating that a method can fail, forcing callers to handle failures.
- **When to Keep Exceptions** [06:45] — Some exceptions should still be thrown, such as unrecoverable scenarios like a missing API key.
- **Match Method Implementation** [09:12] — C# lacks native discriminated unions, but you can implement helper methods like 'match' to simplify result handling.
- **Conclusion on Usage** [11:53] — Use exceptions for broken application states, and results for expected business failures that clients can handle.

## Transcript

like don't use exceptions for flow control are so controversial in the .NET space? I'm talking about using the result pattern to get rid of unnecessary exceptions in your code and my recent post on social media that went pretty
viral. Here's my recent post on X that went fairly viral with over 150k views and all I did here was just showcase a simplified implementation of a result class. It contains a success or failure flag. It wraps an error which consists
of a code and a description letting you represent different kinds of errors in your applications. And then there's a helper method to create a success or a failure result. Obviously, you would also have a result of T in your code
base to also wrap some sort of value where this simple result would be a result of T where T is void. And this little post garnered a lot of attention ranging from why are you even doing this? Just use exceptions. You write
your code to F sharp, go, or rust because they support this and all kinds of nonsense even calling this a terrible idea. And I just wanted to make this video to refute some of these claims and show you a practical example of why
using a result might be valuable and then you'll be the judge of if you want to use this in your applications or not. Here's an example of an application service in a typical enterprise .NET application. It orchestrates a couple of
dependent services, usually some sort of repositories, maybe some payment of the implementation is that we are using some specific exceptions to implement an early return mechanism where we run into a situation in this
method's execution that we can't recover from. So for example, if the concert specified by the concert ID doesn't exist, then we're going to throw a concert not found exception. If there are no more available tickets, we're
going to throw an insufficient seats exception. If the user trying to book a a user not found exception. And you get the idea. We keep throwing some custom that we can't recover from. And in the end we get to the happy path which is
calling our payment gateway to charge the user, creating a ticket, and then storing that inside of our database. Now what's interesting with this approach and how this is usually implemented in most applications is that there's no
global try catch within this method itself handling any really unexpected failures. For example, the payment gateway throwing anything else other than the payment declined exception, our call to the database could also fail for
various reasons. We're not handling any of those in this method. And what you usually have in the top level is some sort of global exception handler or a middleware that's just going to wrap this in a problem details, return it to
the client with more or less information about what went wrong on the back end, and then the client application is supposed to figure out what to do based on the response from the server. And this is exactly the anti-pattern that I
try to avoid when I'm building applications and it's called using exceptions for flow control. What you end up building is a go to statement which is basically what an exception is. It's a jump command at the assembly
somewhere else. In the majority of cases, this go to is going to jump into some global handler that's not doing anything special other than converting this into a problem details response,
can examine later when viewing your structured logs. And what's really exceptions is that they are usually thrown for the exact opposite reason of exception represents something exceptional in your code base where you
can't continue with the normal flow of execution. A concert being null in your database isn't all too exceptional mainly because you can predict it. If you are aware of why this condition might fail, then this isn't really an
exceptions for flow control where I think the result pattern is a much implementation, again simplified. All I have is a result of T, so I'm wrapping and I have helper methods for creating a success or failure result. The error can
be anything you like. It can be an enum, in my case it's a record with a code and a description and a stack trace. Yes, that's right. You don't need to use an exception to get a stack trace. You can create a stack trace anywhere you want.
There's an environment property called a stack trace which you can store when instantiating an error, grab a stack trace where this error was created. So you're not only limited to exceptions to be able to figure out where some sort of
failure occurred. You could also log the stack trace in your structured logs and be able to debug where exactly the failure happened inside of your code refactor because we aren't really changing the behavior of the ticket
booking service to use a result approach, we would start by updating the throwing exceptions, we can use a failure result to represent unexpected scenario. So I would say result ticket failure and then I've got a set of
ticket errors and in this case it's going to be concert not found. So another benefit of this is you can define a static class like in the example here which is going to document the possible failures inside of our
application. So by looking at this, I can figure out that the possible errors are the concert is not found, a concert is not finished, insufficient seats, a error. You can just easily publish this in your CI pipeline into documentation
on your website. So continuing with our refactor, we replace the expected exceptions with a failure type. So instead of throwing an insufficient seats exception, I can return a respective error in a failure result.
Same goes for the user not found exception. I can return a failure result that contains the user not found error by saying result ticket failure and then I've got ticket errors user not found. The next question I often see is does
using a result pattern mean you can't throw exceptions at all? And the answer is absolutely not. This doesn't eliminate all exceptions in your code base, just the expected ones. It also makes your public API more honest.
Previously, this was returning a task of ticket implying that you would always true. In some cases, this method could throw and terminate the execution where the execution would jump to a completely unrelated part of the code base to
handle this exception and return some sort of response. With a result of ticket, we are being honest that this can return a result containing a ticket or it could also possibly fail meaning that it's your responsibility as the
caller of this API to handle a failure scenario. And I think you'll agree that code like this is much easier to reason about rather than if it were using an exception. Now an exception to this and there are exception to all kinds of
rules even when using a result pattern is that some exceptions should simply be thrown. So a typical example of this is some sort of unrecoverable scenario like an API key missing from your payment settings. Forgive me for not being more
came up with. So in this case it's perfectly valid to throw an exception like an invalid configuration exception that will bubble up somewhere and be handled by a global exception handler, that's perfectly fine. You don't have to
where it's not meant to be used. And when you think about it, this really is an exceptional scenario. In all normal execution, you would expect the API key to be present inside of some environment variable or application secret. And if
exceptional and you should throw an exception. Moving on, I'm going to continue removing the expected exceptions and replace them with a failure result. So I'll say result
failure result. So I'll say result ticket failure and then ticket errors. And here we would return payment declined as the respective error. And finally, you have to return some sort of result. So in this case it's going to be
a success result wrapping our object. Now you could also implement an implicit conversion from an object into a success result. I don't think that's necessary here, so I'm going to leave it at that. Now what happens to the code that was
calling our API? I've got a ticket controller here that was using the previous implementation that would be throwing exceptions. And here you could implement something like this, a try block and multiple catch blocks for the
could see. And then based on the type of exception, you could explicitly handle it. Or if it wasn't handled here, then hopefully there's a global catch all handler that's going to handle any missed exceptions. With our new API, we
return a result object that explicitly states that it can succeed or fail and then you can decide how to act on the failure. The problem that we currently some time in the future, it may never be solved, and that is that C# doesn't
natively support discriminated unions. Languages like F#, Go, and Rust have this built in, but C# as of now does not. So you're going to hear this argument quite often, why don't you just use Go or Rust or F#? And the answer is
I simply don't want to. I want to continue using C# and .NET and all this ecosystem has to offer. Also the idea of joining the 10 other F# developers out me. A quick example of what you can also implement on the result object is having
a method like this. I usually call it match and this lets you have a callback where you can access the value of the result and return some sort of T out. Of course, I have to make this generic and this would be our on success callback.
And I also have another callback exposing an error and let's say returning the same value and this would be on failure. And the implementation is very simple. If this is a success result, then we just call on success and
pass in the value. Otherwise, we call on failure and pass in the error. Of course, it's implied that the value would be not null in the success scenario. You can also check for this in our implementation. What this allows me
our implementation. What this allows me to do is say return result match and then my success callback is going to take the value and return it by calling to take the error and then I could have this logic here that checks what the
contents of the error are. So, I'm going to simplify this code a bit to depend on my local error variable and we would have something like this. We could replace the first example with just a method group because it exactly matches
the contract of the okay method. And another thing that's useful is having a switch expression on the error or more specifically the error code where a syntax like mapping this with a static value like this would be really helpful
to return the expected failure. Let's see if I can make this into a switch expression and I'm pretty sure that this isn't valid C# code because it requires a constant value whereas this is a static value. But nonetheless, a syntax
like this that allows you to exhaustively map over the possible failure results would be very helpful to make this a more useful API surface. Another pushback I hear is having to check if this is a success or failure
result like in this example here. And my answer to this is this is kind of the point when you think about it. I'm being explicit that the method I'm calling can fail and also want to handle this failure and see what I can do about it.
Another pushback is having to write if statements in your entire code base to check if a result is a success or failure one and you don't necessarily helper methods like the match method here. And also, you don't really want to
overuse the result pattern everywhere because most of the time it's not the happy path. If there are any exceptions, you can also handle those explicitly and return a value without having to rely on a result. And I'm sure
that people have mentioned. I just don't want to go on rambling about this for too long. Like here's my overall conclusion on when you should use a exception. An exception is the most appropriate solution for when your
application is broken and there's nothing you can do about it. So, the exception and terminating the execution. A result is useful when this is a failure that the client can handle or
something that you can expect and it's a valid part of your business logic like a concert or user being null or having an insufficient number of seats available to book a ticket. You're also forcing the caller to handle this explicitly and
have than depending on exceptions because they are implicit and there are no good ways in C# in my opinion to document that they exist. Yes, C# allows you to document your exceptions using
something like this where if you now hover over this method, you would see this isn't too terrible. However, I still prefer using a result to express my possible failure conditions. Also, if
you want to see why using an exception is expensive and you care about performance, then I recommend taking a look at this video next. Consider leaving a like on this video if you enjoyed it. Thanks a lot for watching
enjoyed it. Thanks a lot for watching and until next time, stay awesome.
