Pattern Matching
Pattern matching lets you ask “what shape is this data?” and act on the answer in one readable switch. It replaces long chains of if, casts and null checks.
On this page · 6 min · Advanced
On this page
- In simple words
- Learning objectives
- Explanation
- Syntax
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 6 min
- Difficulty
- Advanced
- Updated
- Oct 28, 2026
In simple words#
A sorting machine at a post office checks each parcel’s shape and label and drops it into the right bin. Pattern matching checks a value’s type or shape and picks the matching branch.
- is
- Check a type or pattern.
- switch expression
- Compact switch that returns a value.
- Property pattern
- { Age: >= 18 } matches by property values.
Learning objectives#
- Test and cast in one step with is
- Use type, relational and logical patterns
- Match on properties and positions
- Add conditions with when and match lists
Explanation#
is combines a type check and a cast: if (obj is string s) uses s as a string inside the if.
Relational patterns compare: > 0, <= 100. Logical patterns combine them: >= 18 and < 60, not null, "Sat" or "Sun".
Property patterns look inside objects: order is { Country: "IN", Total: >= 1000 }. Positional patterns deconstruct records: Rect(var w, var h).
A when clause adds any extra condition to a case: int n when n % 2 == 0.
List patterns (C# 11) match arrays and lists: [1, ..] means “starts with 1”, [] means empty.
Switch expressions try the arms from top to bottom, and the compiler warns if a case is missing or can never match.
Syntax#
string label = input switch
{
null => "missing",
int n when n < 0 => "negative",
int n => quot;number {n}",
string { Length: 0 } => "empty text",
_ => "other"
};
Code example: Describing anything, pricing shipping#
object?[] items = [42, "hello", 3.5, null, new Circle(2), new Rect(3, 3)];
foreach (var item in items)
{
Console.WriteLine(Describe(item));
}
Console.WriteLine(Shipping(new Order(1200m, "IN", IsPrime: false)));
Console.WriteLine(Shipping(new Order(300m, "IN", IsPrime: false)));
Console.WriteLine(Shipping(new Order(300m, "US", IsPrime: true)));
Console.WriteLine(Shipping(new Order(300m, "US", IsPrime: false)));
int[] numbers = [1, 2, 3];
Console.WriteLine(numbers is [1, ..]);
static string Describe(object? o) => o switch
{
null => "nothing",
int n when n > 0 => quot;positive int {n}",
int n => quot;int {n}",
string s => quot;text of length {s.Length}",
Circle { Radius: > 1 } c => quot;big circle r={c.Radius}",
Rect(var w, var h) when w == h => quot;square {w}x{h}",
_ => quot;something else: {o}"
};
static decimal Shipping(Order order) => order switch
{
{ IsPrime: true } => 0m,
{ Country: "IN", Total: >= 1000m } => 0m,
{ Country: "IN" } => 49m,
_ => 999m
};
record Circle(double Radius);
record Rect(double W, double H);
record Order(decimal Total, string Country, bool IsPrime);
positive int 42 text of length 5 something else: 3.5 nothing big circle r=2 square 3x3 0 49 0 999 True
This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.
Step-by-step explanation#
- Describe checks null first, then types. int n when n > 0 adds a guard.
- Circle { Radius: > 1 } c matches circles with a radius above 1 and names the result c.
- Rect(var w, var h) deconstructs the record into w and h; the when guard keeps only squares.
- 3.5 is a double, which no arm handles, so the discard _ catches it.
- Shipping reads like a pricing table: Prime ships free, big Indian orders ship free, other Indian orders cost 49, everything else 999.
- numbers is [1, ..] is a list pattern: “starts with 1, anything after”.
Real-world use case#
An API maps domain results to HTTP responses with one switch: result switch { NotFound => NotFound(), ValidationFailed v => BadRequest(v.Errors), Success s => Ok(s.Value) }.
Common mistakes#
int n => ... above int n when n > 0 => ... makes the second arm unreachable; the compiler reports error CS8510.
Without _ the switch expression throws SwitchExpressionException for unhandled input. The compiler warns you — do not ignore it.
if (o is string) { var s = (string)o; } does the check twice. Use if (o is string s).
Best practices#
- Put specific patterns before general ones
- Always handle null and _ explicitly
- Prefer is not null over != null
- Keep each arm short; call a method for complex work
Remember this#
is Type name = check + cast in one go.
and / or / not combine patterns.
{ Prop: value } looks inside an object.
Practice exercise#
Your task: Write a switch expression that returns a ticket price by age: under 5 → 0, 5–17 → 100, 60 and over → 120, everyone else → 200.
Need a hint?
Show solution
foreach (int age in new[] { 3, 12, 35, 70 })
{
Console.WriteLine(quot;{age}: {TicketPrice(age)}");
}
// 3: 0, 12: 100, 35: 200, 70: 120
static int TicketPrice(int age) => age switch
{
< 5 => 0,
>= 5 and < 18 => 100,
>= 60 => 120,
_ => 200
};
Mini quiz#
What is printed?
int t = 35;
Console.WriteLine(t switch { < 15 => "Cold", >= 15 and < 30 => "Nice", _ => "Hot" });
Want more? Practise all C# MCQs · C# interview questions
My notes#
Saved in this browser.