Modern C# has evolved far beyond basic if and switch statements, and pattern matching is one of its most expressive additions.
This collection of 20 C# pattern matching exercises covers switch expressions, type patterns, property patterns, and relational patterns, giving you concise, readable ways to branch on the shape and content of your data.
Each exercise includes a Practice Problem, Purpose, Hint, and a full Solution with Explanation, so you understand how to apply modern C# syntax in real scenarios.
Also, See: C# Exercises with 22 topic-wise sets and 620+ practice questions.
What You’ll Practice
- Switch Expressions: Concise, expression-based branching.
- Type Patterns: Matching and casting based on runtime type.
- Property Patterns: Matching on an object’s property values.
- Relational & Logical Patterns: Combining conditions with
and,or, andnot.
+ Table Of Contents (19 Exercises)
Table of contents
- Exercise 1: The Status Converter
- Exercise 2: Boolean Simplifier
- Exercise 3: Day Classifier
- Exercise 4: Safe Downcasting
- Exercise 5: Universal Summer
- Exercise 6: Collection Detector
- Exercise 7: E-Commerce Discount
- Exercise 8: Nested Address Lookup
- Exercise 9: UI Element State
- Exercise 10: Quadrant Finder
- Exercise 11: Login Validator
- Exercise 12: Traffic Light Simulator
- Exercise 13: Age Categorizer
- Exercise 14: Valid Temperature Check
- Exercise 15: Null and Empty Filter
- Exercise 16: Array Start Checker
- Exercise 17: CSV Parser Helper
- Exercise 18: Slice & Dice
- Exercise 19: Matrix Game Matrix
Exercise 1: The Status Converter
Practice Problem: Write a method that takes an integer HTTP status code (e.g., 200, 404, 500) and returns a friendly string message using a basic switch expression.
Purpose: This exercise helps you practice writing a basic switch expression as a concise alternative to a switch statement, useful for simple value-to-value mappings like status codes.
Given Input: statusCode = 404
Expected Output: Not Found
▼ Hint
- Use a switch expression with the form
code switch { ... }. - Match specific literal values like 200, 404, and 500 as individual case arms.
- Use
_as the discard pattern to handle any status code not explicitly listed. - Return the whole switch expression directly from the method.
▼ Solution & Explanation
Explanation:
statusCode switch { ... }: A switch expression evaluates directly to a value, unlike a switch statement which executes a block of code.200 => "OK": Each arm matches a literal value and produces the corresponding string._ => "Unknown Status Code": The discard pattern acts as a default case, catching any status code not explicitly listed.- Expression-bodied method: The switch expression is returned directly using
=>, keeping the method compact.
Exercise 2: Boolean Simplifier
Practice Problem: Create a switch expression that accepts a nullable boolean (bool?) and returns “Yes” for true, “No” for false, and “Maybe” for null.
Purpose: This exercise helps you practice matching against true, false, and null within the same switch expression, showing how nullable value types participate in pattern matching.
Given Input: value = null
Expected Output: Maybe
▼ Hint
- The parameter type must be
bool?so it can hold null in addition to true or false. - Match
trueandfalseas separate literal patterns. - Match
nullexplicitly as its own pattern. - Since these three patterns cover every possible value of
bool?, no discard arm is required.
▼ Solution & Explanation
Explanation:
bool? value: A nullable bool parameter, allowing three possible states: true, false, and null.true => "Yes"andfalse => "No": Match the two literal boolean values directly.null => "Maybe": Matches the absence of a value as its own distinct pattern.- No discard needed: Because true, false, and null cover every possible value of
bool?, the compiler doesn’t require a fallback_arm.
Exercise 3: Day Classifier
Practice Problem: Given an enum DayOfWeek, use pattern matching to return “Weekend” for Saturday and Sunday, and “Weekday” for everything else.
Purpose: This exercise helps you practice combining multiple case labels with the or pattern to group related enum values into a single result.
Given Input: day = DayOfWeek.Saturday
Expected Output: Weekend
▼ Hint
- Use the built-in
System.DayOfWeekenum as the parameter type. - Combine
SaturdayandSundayusing theorpattern in a single case. - Use
_to catch all remaining weekday values in one fallback arm. - Return the whole expression using a switch expression on the enum parameter.
▼ Solution & Explanation
Explanation:
DayOfWeek.Saturday or DayOfWeek.Sunday: Combines two enum values into a single case using theorpattern, avoiding two separate arms with the same result._ => "Weekday": Matches every other day of the week with a single fallback arm.day switch { ... }: The switch expression evaluates the enum value once and returns the matching string.System.DayOfWeek: Reused directly from the framework instead of declaring a custom enum.
Exercise 4: Safe Downcasting
Practice Problem: Write a method DescribeShape(object shape) that returns “It’s a circle with radius R” if it’s a Circle object, “It’s a rectangle” if it’s a Rectangle, and “Unknown shape” otherwise.
Purpose: This exercise helps you practice using type patterns to safely check and cast an object in one step, avoiding a manual is check followed by a separate explicit cast.
Given Input: shape = new Circle(5)
Expected Output: It's a circle with radius 5
▼ Hint
- Define simple
CircleandRectangleclasses with the relevant properties, such asRadiusonCircle. - Use a type pattern like
Circle cinside a switch expression to both check and capture the object as that type. - Reference the captured variable’s properties directly inside that same arm.
- Add a final
_arm to handle any object that isn’t a recognized shape.
▼ Solution & Explanation
Explanation:
Circle c => ...: A type pattern that checks whethershapeis aCircleand, if so, captures it in the variablecso itsRadiuscan be used directly.Rectangle => "It's a rectangle": A type pattern that only needs to confirm the type, so no variable name is required here._ => "Unknown shape": Handles any object that isn’t aCircleor aRectangle, since type patterns never match null either.- Single expression: Replaces what would otherwise be an
ischeck followed by a manual cast, combining both steps into one pattern.
Exercise 5: Universal Summer
Practice Problem: Create a function that accepts an object. If it’s an int, return the int. If it’s a string that can be parsed into an int, parse and return it. Otherwise, return 0.
Purpose: This exercise helps you practice combining type patterns with additional conditions to handle multiple possible input types safely, common when working with loosely typed data.
Given Input: Three calls: ToInt(42), ToInt("100"), ToInt("abc")
Expected Output:
42 100 0
▼ Hint
- Match an
intpattern first, to handle the case where the value is already a number. - Match a
stringpattern, capturing it in a variable so it can be passed toint.TryParse. - Use a
whenclause withTryParseinside that arm to confirm the string can be converted before returning the parsed value. - Add a final
_arm returning 0 for anything else, including strings that can’t be parsed.
▼ Solution & Explanation
Explanation:
int i => i: Matches when the value is already an int, returning it directly without any conversion.string s when int.TryParse(s, out int result) => result: Matches a string only if it can be successfully parsed, using awhenclause to add that extra condition to the pattern.- Failed parse: If
TryParsefails, as with"abc", the pattern doesn’t match, so the switch expression moves on to the next arm instead of throwing an exception. _ => 0: The fallback for any value that is neither an int nor a parseable string.
Exercise 6: Collection Detector
Practice Problem: Write a method that takes an object and returns a string identifying if it is a List<int>, an int[] array, or “Other collection type”.
Purpose: This exercise helps you practice using type patterns to distinguish between different concrete collection types passed as a general object parameter.
Given Input: A List<int>, an int[], and a Dictionary<string, int>, passed one at a time.
Expected Output:
List<int> int array Other collection type
▼ Hint
- Match
List<int>using a type pattern as the first case. - Match
int[]using a type pattern as the second case. - Keep the arms ordered from specific to general, since a switch expression checks patterns from top to bottom.
- Use
_for anything that doesn’t match either specific collection type.
▼ Solution & Explanation
Explanation:
List<int> => "List<int>": Matches only when the object’s runtime type is exactlyList<int>, not other generic list types.int[] => "int array": Matches when the object is an integer array specifically.- Pattern order: More specific patterns are placed before the general fallback, since a switch expression evaluates its arms in order.
_ => "Other collection type": Catches everything else, including other generic collections likeDictionary<string, int>.
Exercise 7: E-Commerce Discount
Practice Problem: Given an Order class with properties TotalPrice (decimal) and IsPremiumCustomer (bool), write a switch expression to calculate discounts: 20% for premium customers with orders over $100, 10% for premium customers under $100, and 0% for everyone else.
Purpose: This exercise helps you practice combining property patterns with additional conditions using when clauses, useful for encoding business rules concisely.
Given Input: Order { TotalPrice = 150, IsPremiumCustomer = true }
Expected Output: Discount = 20%
▼ Hint
- Use a property pattern like
{ IsPremiumCustomer: true }combined with awhenclause checkingTotalPrice. - Order the cases from most specific to least specific, since the first matching arm wins.
- Add a case for premium customers under the $100 threshold with a different discount.
- Add a final
_arm returning 0% for all non-premium customers.
▼ Solution & Explanation
Explanation:
{ IsPremiumCustomer: true } when order.TotalPrice > 100: A property pattern combined with awhenclause, matching only premium customers whose order also exceeds $100.{ IsPremiumCustomer: true } => "Discount = 10%": Matches any remaining premium customer that didn’t satisfy the first, more specific arm.- Arm order matters: The over-$100 premium case is placed first so it is checked before the general premium case.
_ => "Discount = 0%": Covers every non-premium customer, regardless of their total price.
Exercise 8: Nested Address Lookup
Practice Problem: You have a User object containing a Profile object, which contains an Address object with a Country string. Use property patterns to check if the user is from “Canada” in a single line.
Purpose: This exercise helps you practice nested property patterns, which let you reach into related objects several levels deep without writing a chain of null checks.
Given Input: A User whose Profile.Address.Country is "Canada".
Expected Output: User is from Canada
▼ Hint
- Define
User,Profile, andAddressclasses, each holding a reference to the next. - Use a nested property pattern like
{ Profile: { Address: { Country: "Canada" } } }. - This single pattern replaces what would otherwise require three separate null checks.
- Return a different message for users whose nested
Countrydoesn’t match.
▼ Solution & Explanation
Explanation:
{ Profile: { Address: { Country: "Canada" } } }: A nested property pattern that drills intoProfile, thenAddress, thenCountry, all in a single expression.- Implicit null safety: If
ProfileorAddresswere null anywhere along the chain, the pattern simply fails to match instead of throwing aNullReferenceException. - Compared to manual checks: This replaces what would otherwise require checking
user.Profile != null, thenuser.Profile.Address != null, before finally comparingCountry. _ => "User is not from Canada": Handles every case where the nested pattern doesn’t match, including a nullProfileorAddress.
Exercise 9: UI Element State
Practice Problem: Create a switch expression that evaluates a Button control based on its properties: IsEnabled = false returns “Disabled”, IsHovered = true returns “Highlighted”, and IsPressed = true returns “Clicked”.
Purpose: This exercise helps you practice ordering multiple property patterns so the most important state takes priority over the others.
Given Input: Button { IsEnabled = true, IsHovered = false, IsPressed = true }
Expected Output: Clicked
▼ Hint
- Define a
Buttonclass withIsEnabled,IsHovered, andIsPressedboolean properties. - Check
IsEnabled: falsefirst, since a disabled button should report its state regardless of hover or press. - Check
IsHovered: truenext, followed byIsPressed: true, matching the priority order given in the problem. - Add a final case or discard for the default, unhighlighted and unpressed state.
▼ Solution & Explanation
Explanation:
{ IsEnabled: false } => "Disabled": Checked first, since a disabled button should report its state regardless of hover or press flags.{ IsHovered: true } => "Highlighted": Checked next, taking priority over the pressed state in this ordering.{ IsPressed: true } => "Clicked": Only reached if the button is enabled and not currently hovered._ => "Normal": The fallback for a button that is enabled, not hovered, and not pressed.
Exercise 10: Quadrant Finder
Practice Problem: Create a Point record with int X and int Y. Use a positional pattern to determine which quadrant the point lies in (e.g., return “Quadrant 1” if both X and Y are positive).
Purpose: This exercise helps you practice positional patterns with records, which let you deconstruct a record’s properties directly inside a switch expression.
Given Input: Point(3, 4)
Expected Output: Quadrant 1
▼ Hint
- Declare
Pointas a record with positional parametersXandY. - Use a positional pattern like
(> 0, > 0)to match both coordinates being positive at once. - Add separate arms for the other three quadrant combinations.
- Include a discard arm for points that lie on an axis, where
XorYequals zero.
▼ Solution & Explanation
Explanation:
record Point(int X, int Y): A record with positional parameters, which automatically supports deconstruction intoXandYfor pattern matching.(> 0, > 0) => "Quadrant 1": A positional pattern that deconstructs the point and applies a relational pattern to each coordinate in one step.- Relational patterns: Patterns like
> 0and< 0check numeric ranges directly inside the pattern instead of requiring separateifconditions. _ => "On an axis": Handles any point whereXorYis exactly zero, since none of the four quadrant patterns would match.
Exercise 11: Login Validator
Practice Problem: Given a tuple (string username, string password), use pattern matching to return true if the credentials match a specific admin account, and false otherwise.
Purpose: This exercise helps you practice matching against tuple patterns, letting you check multiple values together in a single switch expression instead of chaining separate comparisons.
Given Input: Two login attempts: ("admin", "P@ssw0rd") and ("admin", "wrongpass").
Expected Output:
True False
▼ Hint
- Declare the parameter type as a tuple,
(string username, string password). - Use a tuple pattern like
("admin", "P@ssw0rd")to match both values at the same time. - Add a discard pattern
_as the final arm to return false for any other combination. - Call the method twice with different credentials to see both outcomes.
▼ Solution & Explanation
Explanation:
(string username, string password) credentials: A tuple parameter that groups the username and password into a single value.("admin", "P@ssw0rd") => true: A tuple pattern that matches only when both elements equal the specified literals at the same time._ => false: Catches every other combination, including a correct username paired with an incorrect password.- Single expression: Replaces what would otherwise require two separate
==comparisons joined by&&.
Exercise 12: Traffic Light Simulator
Practice Problem: Given a tuple of (CurrentLight, PedestrianWaiting), return the next state of the traffic light (e.g., if it’s Red and a pedestrian is waiting, keep it Red; if no one is waiting, turn it Green).
Purpose: This exercise helps you practice building a small state machine using tuple patterns, where the combination of two values together determines the result, not either value on its own.
Given Input: (current = Red, pedestrianWaiting = true)
Expected Output: Red
▼ Hint
- Represent the input as a tuple,
(LightState current, bool pedestrianWaiting). - Match
(Red, true)to keep the light red while a pedestrian is still waiting. - Match
(Red, false)to allow the light to change to green once no one is waiting. - Add the remaining tuple patterns to complete the full state machine for Yellow and Green.
▼ Solution & Explanation
Explanation:
(LightState current, bool pedestrianWaiting): A tuple parameter combining the current light and whether a pedestrian is waiting.(LightState.Red, true) => LightState.Red: Matches both elements together, keeping the light red as long as a pedestrian is still waiting.(LightState.Red, false) => LightState.Green: Matches when no pedestrian is waiting, allowing the light to change.(LightState.Yellow, _) => LightState.Red: Uses a discard for the second element since a yellow light always moves to red regardless of pedestrian status.
Exercise 13: Age Categorizer
Practice Problem: Write a switch expression that takes an int age and uses relational patterns to return “Child” (under 13), “Teenager” (13 to 19), “Adult” (20 to 64), and “Senior” (65 or older).
Purpose: This exercise helps you practice relational patterns combined with the and pattern to express inclusive ranges directly inside a switch expression.
Given Input: age = 45
Expected Output: Adult
▼ Hint
- Use
< 13as a relational pattern for the child case. - Combine
>= 13and<= 19using theandpattern for the teenager range. - Combine
>= 20and<= 64using theandpattern for the adult range. - Use
>= 65as the final relational pattern for the senior case.
▼ Solution & Explanation
Explanation:
< 13 => "Child": A relational pattern matching any age below 13.>= 13 and <= 19 => "Teenager": Combines two relational patterns with theandpattern to express an inclusive range.>= 20 and <= 64 => "Adult": Applies the same technique to the adult range.>= 65 => "Senior": A single relational pattern covers every remaining age, since it’s the highest range in the sequence.
Exercise 14: Valid Temperature Check
Practice Problem: Write a method that returns true if a temperature double is within a safe operating range: greater than 0.0 and less than or equal to 100.0.
Purpose: This exercise helps you practice combining two relational patterns with and inside a single is expression, an alternative to writing separate comparisons joined by &&.
Given Input: temperature = 37.5
Expected Output: True
▼ Hint
- Use the
iskeyword with a pattern rather than a switch expression, since this method only needs a single true or false result. - Combine
> 0.0and<= 100.0using theandpattern inside that singleisexpression. - Return the result of the
isexpression directly from the method. - Test a value outside the range separately to confirm the method returns false.
▼ Solution & Explanation
Explanation:
temperature is > 0.0 and <= 100.0: A single pattern-matching expression combining two relational patterns, equivalent totemperature > 0.0 && temperature <= 100.0.andpattern: Requires the value to satisfy both relational conditions at once rather than either one alone.isexpression: Returns a bool directly, making it well suited for a single validation check instead of a full switch expression.- Boundary behavior: A temperature of exactly
100.0still returns true, while0.0itself returns false, since the lower bound is exclusive.
Exercise 15: Null and Empty Filter
Practice Problem: Write a pattern that checks if an object is not null and is not an empty string.
Purpose: This exercise helps you practice using the not pattern to express negative conditions clearly, instead of relying on != and manual length checks.
Given Input: Two values: "Hello" and "".
Expected Output:
True False
▼ Hint
- Use the
notpattern to require that the value is not null. - Combine it with another
notpattern checking against the empty string constant"". - Join both conditions using
andso the value must satisfy each one independently. - Test both a normal string and an empty string to see the difference in the result.
▼ Solution & Explanation
Explanation:
not null: A negated pattern that matches any value except null.not "": A negated constant pattern that matches any value except the specific empty string.and: Requires both negated patterns to hold true, so a null value or an empty string returns false.object value: Because the parameter type isobject, the same check works for any value, not just strings, since a non-string value would already satisfynot "".
Exercise 16: Array Start Checker
Practice Problem: Write a method that accepts an int[] and returns true if the array starts with the numbers 1, 2, 3, regardless of what follows.
Purpose: This exercise helps you practice using a list pattern with a trailing slice pattern to check the beginning of a sequence without caring about its remaining length.
Given Input: numbers = [1, 2, 3, 4, 5]
Expected Output: True
▼ Hint
- Use a list pattern with square brackets to match against the array’s elements directly.
- Specify the first three elements as literal values:
1, 2, 3. - Follow those with the slice pattern
..to allow any number of additional elements afterward. - Test with an array that doesn’t start with
1, 2, 3to confirm it returns false.
▼ Solution & Explanation
Explanation:
numbers is [1, 2, 3, ..]: A list pattern matching an array whose first three elements are exactly 1, 2, and 3...(slice pattern): Matches any number of remaining elements, including zero, so the array can be any length as long as it starts correctly.- List patterns: Work directly against arrays and other countable, indexable types without needing to call methods like
Takeor manually index into the array. - Short-circuit behavior: If the array has fewer than three elements, the pattern simply fails to match instead of throwing an exception.
Exercise 17: CSV Parser Helper
Practice Problem: Given a string[] representing a split CSV row, match rows that have exactly three elements where the first element is “ID” and the last is “Active”.
Purpose: This exercise helps you practice combining an exact-length list pattern with specific literal values at fixed positions, while leaving the middle element unconstrained.
Given Input: row = ["ID", "42", "Active"]
Expected Output: True
▼ Hint
- Use a list pattern with exactly three elements inside the square brackets.
- Match the first element against the literal
"ID". - Use a discard
_for the middle element, since its value doesn’t matter. - Match the last element against the literal
"Active".
▼ Solution & Explanation
Explanation:
row is ["ID", _, "Active"]: A list pattern requiring exactly three elements, since no slice pattern is present."ID"and"Active": Literal patterns pinning the first and last elements to specific values._: A discard pattern for the middle element, matching any value without capturing it.- Exact length requirement: A row with two elements or four elements would not match this pattern at all, regardless of its content.
Exercise 18: Slice & Dice
Practice Problem: Write a list pattern using the discard (_) and slice (..) operators to check if an array contains at least two elements, where the first element is 0 and the last element is 9.
Purpose: This exercise helps you practice combining the slice pattern with fixed first and last elements to validate the ends of a sequence while ignoring everything in between.
Given Input: numbers = [0, 4, 7, 9]
Expected Output: True
▼ Hint
- Use a list pattern with the first element matched against
0. - Follow it with the slice pattern
..to allow any number of elements in between, including none. - Match the last element against
9as the final element in the pattern. - Test with an array of exactly two elements,
[0, 9], to confirm the slice pattern can also match zero elements in between.
▼ Solution & Explanation
Explanation:
numbers is [0, .., 9]: A list pattern pinning the first element to0and the last element to9, with..absorbing everything in between...in the middle: Matches zero or more elements, so both[0, 9]and[0, 4, 7, 9]satisfy the same pattern.- Minimum length: The pattern still requires at least two elements, since the first and last positions cannot overlap.
- No extra discard needed: The slice pattern already ignores its contents entirely, so a separate
_for the middle section isn’t required.
Exercise 19: Matrix Game Matrix
Practice Problem: Given an array of char arrays representing a Tic-Tac-Toe board, use list patterns to check if the first row has a winning line of ['X', 'X', 'X'].
Purpose: This exercise helps you practice applying a list pattern to one row of a two-dimensional structure, useful for validating game state or grid-based logic.
Given Input: A board whose first row is ['X', 'X', 'X'].
Expected Output: First row is a winning line
▼ Hint
- Represent the board as a jagged array,
char[][], where each inner array is one row. - Access the first row with
board[0]and apply a list pattern directly to it. - Match the row against
['X', 'X', 'X']to check for a winning line of X’s. - Return a different message if the first row doesn’t match that exact pattern.
▼ Solution & Explanation
Explanation:
board[0]: Extracts the first row from the jagged array before the pattern is applied to it.['X', 'X', 'X']: A list pattern requiring exactly three elements, each matching the literal character'X'.- Switch expression on a single row: Applies the same list pattern technique used for one-dimensional arrays, just to a row extracted from a larger grid.
_ => "First row is not a winning line": Handles any row that doesn’t consist of exactly three X’s, including rows with a mix of X, O, or a different length.

Leave a Reply