Robust C# programs need to handle unexpected situations gracefully, and that means understanding try, catch, finally, and how to design your own custom exception types.
This collection of 20 C# exception handling exercises covers catching specific exception types, cleaning up resources safely, and throwing meaningful custom exceptions instead of letting programs crash.
Each exercise includes a Practice Problem, Purpose, Hint, and a full Solution with Explanation, so you learn to write code that fails safely and predictably.
Also, See: C# Exercises with 22 topic-wise sets and 620+ practice questions.
+ Table Of Contents (20 Exercises)
Table of contents
- Exercise 1: Handle a Divide by Zero Error
- Exercise 2: Handle an Out of Range Array Index
- Exercise 3: Handle an Invalid Number Format
- Exercise 4: Handle a Null Reference Error
- Exercise 5: Catch Any Exception with a Generic Handler
- Exercise 6: Handle Multiple Exception Types
- Exercise 7: Guarantee Cleanup with Finally
- Exercise 8: Validate a Property with a Custom Exception
- Exercise 9: Detect Integer Overflow
- Exercise 10: Handle a Missing Dictionary Key
- Exercise 11: Create a Custom Exception Type
- Exercise 12: Filter Exceptions with a When Clause
- Exercise 13: Wrap and Preserve an Inner Exception
- Exercise 14: Refactor Cleanup Code with a Using Statement
- Exercise 15: Handle an Invalid Cast Safely
- Exercise 16: Catch Unhandled Exceptions Globally
- Exercise 17: Compare the Performance of Try-Catch vs TryParse
Exercise 1: Handle a Divide by Zero Error
Practice Problem: Write a program that prompts the user for two integers and divides them. Use a try-catch block to handle DivideByZeroException if the user enters 0 as the divisor.
Purpose: This exercise helps you practice wrapping risky arithmetic in a try-catch block so a predictable error like division by zero does not crash the entire program.
Given Input: numerator = 10, divisor = 0
Expected Output: Error: Cannot divide by zero. Attempted to divide by zero.
▼ Hint
- Place the division inside a
tryblock, then add acatch (DivideByZeroException ex)block after it. - Use
ex.Messageinside the catch block to include the built-in exception message in your own error output.
▼ Solution & Explanation
Explanation:
numerator / divisor: Integer division by zero throws aDivideByZeroExceptionat runtime instead of returning a value like floating point division would.catch (DivideByZeroException ex): Catches specifically this exception type, letting the program respond with a clear message instead of terminating.ex.Message: Exposes the built-in description of what went wrong, which can be combined with your own custom text.
Exercise 2: Handle an Out of Range Array Index
Practice Problem: Create an array of 5 colors. Ask the user to input an index to print a color. Handle IndexOutOfRangeException gracefully if they pick an index like 10.
Purpose: This exercise helps you practice catching IndexOutOfRangeException, which is thrown whenever code accesses an array position that does not exist.
Given Input: colors = { "Red", "Green", "Blue", "Yellow", "Purple" }, index = 10
Expected Output: Error: Index 10 is out of range. Index was outside the bounds of the array.
▼ Hint
- Wrap the array access,
colors[index], inside atryblock. - Add a
catch (IndexOutOfRangeException ex)block to print a friendly message instead of letting the program crash.
▼ Solution & Explanation
Explanation:
colors[index]: Accessing index 10 on a 5 element array is invalid, since valid indexes only range from 0 to 4.catch (IndexOutOfRangeException ex): Intercepts the runtime error and lets the program report the invalid index instead of terminating unexpectedly.
Exercise 3: Handle an Invalid Number Format
Practice Problem: Write a program that asks the user for their age. Use int.Parse(). Catch FormatException if they type “twenty” instead of a number.
Purpose: This exercise helps you practice catching FormatException, which is thrown whenever int.Parse() receives text that cannot be converted into a number.
Given Input: ageInput = "twenty"
Expected Output: Error: "twenty" is not a valid number. The input string 'twenty' was not in a correct format.
▼ Hint
- Call
int.Parse(ageInput)inside atryblock rather thanint.TryParse(), sinceParse()is what actually throws the exception. - Catch
FormatExceptionspecifically to handle the case where the text does not represent a valid integer.
▼ Solution & Explanation
Explanation:
int.Parse(ageInput): Attempts to convert the text into an integer, throwing aFormatExceptionimmediately when the text is not a valid number.catch (FormatException ex): Catches the parsing failure so the program can explain the problem instead of crashing.
Exercise 4: Handle a Null Reference Error
Practice Problem: Create a string variable initialized to null. Try to invoke .ToUpper() on it. Wrap it in a try-catch to handle NullReferenceException.
Purpose: This exercise helps you practice recognizing and catching NullReferenceException, one of the most common runtime errors, caused by calling a method on a variable that holds no object.
Given Input: name = null
Expected Output: Error: Cannot call a method on a null reference. Object reference not set to an instance of an object.
▼ Hint
- Call
name.ToUpper()inside atryblock, wherenameis assignednullinstead of an actual string. - Catch
NullReferenceExceptionto intercept the failure that occurs from calling a method on a null value.
▼ Solution & Explanation
Explanation:
name.ToUpper(): Calling any method on a variable holdingnullthrows aNullReferenceException, since there is no actual object to call the method on.catch (NullReferenceException ex): Catches the failure so the program can report it clearly instead of terminating with an unhandled error.
Exercise 5: Catch Any Exception with a Generic Handler
Practice Problem: Write a program that simulates a random error generator (throwing different exceptions). Use a generic catch (Exception ex) block to log the error message and the exception type to the console.
Purpose: This exercise helps you practice writing a single, general purpose catch block that can handle any exception type, along with inspecting the exception’s runtime type using GetType().
Given Input: errorChoice = 2, which triggers an ArgumentException
Expected Output:
Caught Exception Type = ArgumentException Message = Invalid argument provided.
▼ Hint
- Write a helper method that throws a different exception type depending on an input value, using a
switchstatement. - Catch everything with a single
catch (Exception ex)block, since it is the base type that every exception inherits from. - Use
ex.GetType().Nameto print the specific exception class that was actually thrown and caught.
▼ Solution & Explanation
Explanation:
catch (Exception ex): Since every exception type ultimately inherits fromException, this single block can catch any of them.ex.GetType().Name: Returns the actual runtime class name of the caught exception, even though the variable is declared as the generalExceptiontype.throw new ArgumentException("Invalid argument provided."): Creates and throws a specific exception type with a custom message describing what went wrong.
Exercise 6: Handle Multiple Exception Types
Practice Problem: Write a method that reads a file path from the user, opens the file, and parses the first line into an integer. Implement multiple catch blocks to handle FileNotFoundException, FormatException, and a generic Exception.
Purpose: This exercise helps you practice stacking multiple catch blocks in order from most specific to least specific, so each type of failure gets its own tailored message.
Given Input: filePath = "data.txt" (a file that does not exist at that location)
Expected Output: Error: File not found. Could not find file 'data.txt'.
Note: the exact output depends on whether data.txt exists when the program runs. If the file exists but its first line is not a number, the FormatException block runs instead.
▼ Hint
- Order the catch blocks from most specific to least specific:
FileNotFoundException, thenFormatException, thenException. - Use a
usingstatement around theStreamReaderso the file is disposed of automatically once reading finishes or an exception occurs.
▼ Solution & Explanation
Explanation:
using (StreamReader reader = new StreamReader(filePath)): Opens the file for reading and guarantees the reader is closed automatically once the block finishes, whether or not an exception occurs.- Catch order: The catch blocks are listed from most specific (
FileNotFoundException) to least specific (Exception), which is required since C# checks them top to bottom. catch (Exception ex): Acts as a final safety net for any error that does not match the two more specific catch blocks above it.
Exercise 7: Guarantee Cleanup with Finally
Practice Problem: Write a program that opens a StreamWriter to write data to a file. Use a try-finally (or try-catch-finally) block to ensure the writer is explicitly closed and disposed of, even if an error occurs during writing.
Purpose: This exercise helps you practice using a finally block to guarantee cleanup code always runs, whether the try block succeeds or throws an exception.
Given Input: filePath = "output.txt"
Expected Output:
Data written successfully. Writer closed.
▼ Hint
- Declare the
StreamWritervariable above thetryblock so it is still in scope inside thefinallyblock. - In the
finallyblock, check whether the writer is notnullbefore calling.Close()on it.
▼ Solution & Explanation
Explanation:
finally: The code inside this block always runs after thetryblock finishes, regardless of whether an exception was thrown or caught.if (writer != null): Guards against calling.Close()on a writer that was never successfully created, for example if the file path itself was invalid.writer.Close(): Flushes any buffered data to disk and releases the file handle, ensuring the file is not left open.
Exercise 8: Validate a Property with a Custom Exception
Practice Problem: Create a Person class with an Age property. In the setter, if the age is less than 0 or greater than 120, throw an ArgumentOutOfRangeException with a custom message.
Purpose: This exercise helps you practice throwing your own exception from inside a property setter, so invalid state can never be assigned to an object in the first place.
Given Input: person.Age = 150
Expected Output: Error: Age must be between 0 and 120. (Parameter 'value')
▼ Hint
- Inside the
setaccessor, checkvalue < 0 || value > 120before assigning it to the backing field. - Use
throw new ArgumentOutOfRangeException(nameof(value), "your message")to include both the parameter name and a custom explanation.
▼ Solution & Explanation
Explanation:
if (value < 0 || value > 120): Validates the incoming value inside the setter before it is ever stored in the backing field.throw new ArgumentOutOfRangeException(nameof(value), "..."): Throws a built-in exception type intended specifically for values that fall outside an acceptable range, paired with a custom explanation.nameof(value): Refers to the setter’s implicitvalueparameter by name, so the exception clearly identifies which parameter caused the failure.
Exercise 9: Detect Integer Overflow
Practice Problem: Write a program that multiplies two large integers (e.g., int.MaxValue * 2) inside a checked block. Catch the resulting OverflowException.
Purpose: This exercise helps you practice using the checked keyword to force arithmetic overflow to throw an exception instead of silently wrapping around to an incorrect value.
Given Input: int.MaxValue * 2
Expected Output: Error: Arithmetic operation overflowed. Arithmetic operation resulted in an overflow.
▼ Hint
- Wrap the multiplication in a
checked { ... }block, since normal unchecked arithmetic silently overflows without throwing. - Catch
OverflowExceptionaround the checked block to handle the case where the result is too large to fit in anint.
▼ Solution & Explanation
Explanation:
checked { ... }: Forces the enclosed arithmetic to be checked for overflow at runtime, throwing an exception instead of wrapping the value around silently.int.MaxValue * 2: Multiplying the largest possibleintvalue by 2 produces a result that cannot fit within the 32-bit range ofint.catch (OverflowException ex): Catches the overflow specifically, allowing the program to report it clearly instead of continuing with a corrupted value.
Exercise 10: Handle a Missing Dictionary Key
Practice Problem: Create a lookup dictionary. Try to access a key that doesn’t exist. Catch the KeyNotFoundException and display a friendly message showing available keys.
Purpose: This exercise helps you practice catching KeyNotFoundException, thrown when indexing directly into a dictionary with a key that is not present, and using the caught error to guide the user toward valid options.
Given Input: A dictionary with keys Keyboard, Mouse, and Monitor. requestedKey = "Webcam"
Expected Output:
Error: Key "Webcam" was not found. The given key 'Webcam' was not present in the dictionary. Available Keys = Keyboard, Mouse, Monitor
▼ Hint
- Accessing a dictionary with square bracket syntax,
stock[requestedKey], throwsKeyNotFoundExceptionwhen the key is missing, unlikeTryGetValue()which does not throw. - Inside the catch block, use
string.Join(", ", stock.Keys)to list all the keys that are actually available.
▼ Solution & Explanation
Explanation:
stock[requestedKey]: Indexing directly into the dictionary throwsKeyNotFoundExceptionwhen the requested key is not present.string.Join(", ", stock.Keys): Builds a readable, comma separated list of every key currently in the dictionary to help guide the user.- Alternative: Using
stock.TryGetValue(requestedKey, out int quantity)instead would avoid the exception entirely, but this exercise focuses specifically on catchingKeyNotFoundException.
Exercise 11: Create a Custom Exception Type
Practice Problem: Create a banking application simulation. Write a custom exception named InsufficientFundsException. Throw it when a user tries to withdraw more money than their current balance.
Purpose: This exercise helps you practice defining your own exception class by inheriting from Exception, giving you a domain specific error type instead of relying only on built-in exceptions.
Given Input: Balance = 100, withdrawal amount = 250
Expected Output: Error: Cannot withdraw $250. Current balance is $100.
▼ Hint
- Create a class named
InsufficientFundsExceptionthat inherits fromException, with a constructor that passes its message to the base class. - In the
Withdraw()method, compare the requested amount to the current balance before subtracting it, and throw the custom exception if the amount is too high.
▼ Solution & Explanation
Explanation:
class InsufficientFundsException : Exception: Defines a new exception type by inheriting from the baseExceptionclass, gaining all of its standard behavior.: base(message): Forwards the custom message up to the baseExceptionconstructor, so it becomes available through the familiarex.Messageproperty.catch (InsufficientFundsException ex): Catches the custom exception type specifically, the same way you would catch any built-in exception.
Exercise 12: Filter Exceptions with a When Clause
Practice Problem: Write a program that simulates an HTTP web request failure. Throw an exception with an error code, and use the catch (Exception ex) when (...) syntax to only catch the exception if the error code is 404.
Purpose: This exercise helps you practice exception filters, which let a catch block run only when an extra condition is true, instead of catching every exception of that type.
Given Input: errorCode = 404
Expected Output: Error: Resource not found (404). The HTTP request failed.
▼ Hint
- Give your custom exception an
ErrorCodeproperty so the filter has something to check against. - Write the filter as
catch (HttpRequestFailedException ex) when (ex.ErrorCode == 404), placed before a broader catch block for the same exception type.
▼ Solution & Explanation
Explanation:
when (ex.ErrorCode == 404): Adds an extra condition to the catch block, so it only activates when the exception’sErrorCodeis specifically 404.- Catch order: Since both catch blocks handle the same exception type, the filtered one must appear first, otherwise it would never be reached.
- Unmatched filter: If the filter condition evaluates to false, execution falls through to the next matching catch block instead of the filtered one.
Exercise 13: Wrap and Preserve an Inner Exception
Practice Problem: Write a method DataLayer() that throws a SqlException (or a simulated database error). Call it from a BusinessLayer() method, catch it, wrap it inside a new ApplicationException, and throw the new one while preserving the original as the InnerException.
Purpose: This exercise helps you practice wrapping a low level exception in a higher level, more meaningful exception without losing the original error details needed for debugging.
Given Input: DataLayer() simulates a database connection failure instead of using an actual SqlException, since that type requires a separate database library reference.
Expected Output:
Outer Message = A business layer error occurred while accessing data. Inner Message = Simulated database connection failure.
▼ Hint
- Catch the low level exception inside
BusinessLayer(), then construct a newApplicationExceptionpassing the original exception as its second constructor argument. - The
ApplicationException(string message, Exception innerException)constructor automatically stores the original exception in theInnerExceptionproperty.
▼ Solution & Explanation
Explanation:
throw new ApplicationException("...", ex): Creates a new, higher level exception while attaching the original exceptionexas itsInnerException.ex.InnerException.Message: Reaches back through the wrapper to read the message from the original low level exception.- Preserved context: Wrapping the exception this way means callers of
BusinessLayer()see a meaningful, high level error while the original cause is never lost.
Exercise 14: Refactor Cleanup Code with a Using Statement
Practice Problem: Take a standard try-catch-finally block used for database connections or file I/O, and refactor it using the modern using statement/declaration to demonstrate implicit exception-safe resource disposal.
Purpose: This exercise helps you practice replacing a manual try-finally cleanup pattern with a using block, which disposes of the resource automatically even if an exception is thrown.
Given Input: filePath = "log.txt"
Expected Output:
Log entry written successfully. Writer disposed automatically.
▼ Hint
- The manual version would declare the writer above a
tryblock, use it inside, and call.Dispose()or.Close()inside afinallyblock. - Replace all of that with
using (StreamWriter writer = new StreamWriter(filePath)) { ... }, sinceStreamWriterimplementsIDisposableand gets disposed automatically once the block ends.
▼ Solution & Explanation
Explanation:
using (StreamWriter writer = ...): Replaces the manualtry-finallypattern entirely, since the compiler generates the equivalent cleanup logic automatically.- Automatic disposal: Once execution leaves the
usingblock, whether normally or due to an exception,writer.Dispose()is called behind the scenes. - Less boilerplate: The refactored version removes the need for a nullable variable declared outside the block and a manual null check inside
finally.
Exercise 15: Handle an Invalid Cast Safely
Practice Problem: Create a list of objects containing a mix of strings, integers, and custom classes. Loop through the list and try to explicitly cast every object to a string. Handle InvalidCastException for the non-string items so the loop continues.
Purpose: This exercise helps you practice catching InvalidCastException inside a loop, so a single bad cast does not stop the entire iteration from completing.
Given Input: items = { "Apple", 42, new Product { Name = "Chair" }, "Banana" }
Expected Output:
Valid String = Apple Skipped Item: Cannot cast Int32 to string. Unable to cast object of type 'System.Int32' to type 'System.String'. Skipped Item: Cannot cast Product to string. Unable to cast object of type 'FileHandlingExercises.Product' to type 'System.String'. Valid String = Banana
▼ Hint
- Store the mixed values in a
List<object>, since it can hold any type. - Inside the
foreachloop, wrap the explicit cast(string)itemin its owntry-catchso one failed cast does not exit the loop early.
▼ Solution & Explanation
Explanation:
(string)item: An explicit cast that throwsInvalidCastExceptionat runtime whenever the underlying object is not actually astring.try-catchinside the loop: Placing the try-catch inside theforeachbody, rather than around the whole loop, allows every remaining item to still be processed after a failed cast.item.GetType().Name: Reports the actual runtime type of the skipped item, making the error message more informative than a generic failure notice.
Exercise 16: Catch Unhandled Exceptions Globally
Practice Problem: Create a console application or a small Web API project and implement a global unhandled exception handler (e.g., using AppDomain.CurrentDomain.UnhandledException or an ASP.NET Core Middleware) to catch errors that escape local try-catch blocks.
Purpose: This exercise helps you practice subscribing to AppDomain.CurrentDomain.UnhandledException, a last resort notification point for exceptions that were never caught anywhere else in the application.
Given Input: ThrowUnhandledError() throws an exception with no surrounding try-catch.
Expected Output:
Application starting. Global Handler Caught: Something went wrong outside any try-catch block.
Note: AppDomain.CurrentDomain.UnhandledException only notifies you that an unhandled exception occurred, it does not stop the process from terminating afterward. It is meant for logging and diagnostics, not for recovering execution.
▼ Hint
- Subscribe to
AppDomain.CurrentDomain.UnhandledExceptionwith a lambda near the very start ofMain(), before anything risky runs. - The event handler receives an
UnhandledExceptionEventArgs, whoseExceptionObjectproperty must be cast toExceptionto read its message.
▼ Solution & Explanation
Explanation:
AppDomain.CurrentDomain.UnhandledException += ...: Registers a handler that runs whenever an exception propagates all the way up without being caught anywhere in the application.(Exception)e.ExceptionObject: The event exposes the exception as a plainobject, so it needs to be cast back toExceptionbefore reading itsMessage.- Not a substitute for try-catch: This handler is only useful for logging or last-chance diagnostics, since the process still terminates once it finishes running.
Exercise 17: Compare the Performance of Try-Catch vs TryParse
Practice Problem: Write two methods that process 10,000 invalid string inputs into integers. Method A uses try-catch with int.Parse(). Method B uses int.TryParse(). Benchmark the execution time of both to observe the performance cost of throwing thousands of exceptions.
Purpose: This exercise helps you practice measuring the real performance cost of exceptions, since throwing and catching an exception is far more expensive than a simple conditional check.
Given Input: An array of 10,000 copies of the invalid string "not-a-number".
Expected Output:
Method A (try-catch with Parse) = 850 ms Method B (TryParse) = 4 ms
Note: the exact millisecond values will vary significantly depending on your machine and the .NET runtime version, but Method A should consistently be dramatically slower than Method B.
▼ Hint
- Use the
Stopwatchclass fromSystem.Diagnosticsto measure elapsed time around each method call. - Method A should call
int.Parse()inside atry-catchthat silently ignores theFormatException, while Method B should callint.TryParse()and ignore its boolean result.
▼ Solution & Explanation
Explanation:
Stopwatch.StartNew(): Creates and immediately starts a timer, providing a simple way to measure how long each method takes to run.int.Parse()inside try-catch: Every single failed parse throws and catches a real exception, and exception handling carries significant overhead compared to normal control flow.int.TryParse(): Reports success or failure through a boolean return value instead of throwing, avoiding exception overhead entirely for expected, everyday invalid input.

Leave a Reply