Pattern Matching
C# / .NET Interview Notes
1. Big Picture
Pattern matching in C# lets you inspect data and make decisions based on its shape, type, or value — all in a concise expression. It evolved from simple switch statements into a powerful feature spanning is, switch expressions, and property/positional patterns.
2. Type Pattern (is)
The simplest pattern — checks if an object is a type and casts it in one step:
object obj = "hello";
// Old way
if (obj is string)
{
string s = (string)obj;
Console.WriteLine(s.Length);
}
// Pattern matching way
if (obj is string s)
{
Console.WriteLine(s.Length); // s is already string
}
Common Type Patterns
if (obj is int n) { /* n is int */ }
if (obj is string s) { /* s is string */ }
if (obj is not null) { /* obj is not null */ }
if (obj is null) { /* obj is null */ }
3. Switch Expression
A more expressive replacement for switch statements — returns a value:
string DayName(int day) => day switch
{
1 => "Monday",
2 => "Tuesday",
3 => "Wednesday",
4 => "Thursday",
5 => "Friday",
6 or 7 => "Weekend",
_ => "Invalid" // default
};
=> and are exhaustive — the compiler warns if you miss a case.
4. Property Pattern
Match on the properties of an object, not just its type:
record Person(string Name, int Age, string City);
var p = new Person("Alice", 30, "NYC");
// Match on property values
if (p is { Age: > 18, City: "NYC" })
{
Console.WriteLine("Adult in NYC");
}
// Match on nested properties
if (p is { Name.Length: > 3 })
{
Console.WriteLine("Long name");
}
Property Pattern Syntax
| Pattern | Meaning |
|---|---|
{ Age: > 18 } | Age greater than 18 |
{ Name: "Alice" } | Name equals "Alice" |
{ City: not null } | City is not null |
{ Age: >= 18 and <= 65 } | Age in range |
{ Name.Length: > 3 } | Nested property access |
5. Positional Pattern
Deconstruct an object and match on its positional values:
record Point(int X, int Y);
var pt = new Point(3, 4);
// Positional deconstruction
if (pt is ( > 0, > 0 ))
{
Console.WriteLine("First quadrant");
}
// With variable binding
if (pt is (var x, var y) && x == y)
{
Console.WriteLine("On diagonal");
}
6. when Guard Clause
Add extra conditions to any pattern:
string Classify(int n) => n switch
{
> 0 when n % 2 == 0 => "Positive even",
> 0 when n % 2 != 0 => "Positive odd",
< 0 => "Negative",
0 => "Zero",
_ => "Unknown"
};
7. and / or / not Combinators
Combine patterns with logical operators:
int age = 25;
// and
if (age is > 18 and < 65) { /* working age */ }
// or
if (age is < 13 or > 65) { /* dependent */ }
// not
if (age is not 0) { /* non-zero */ }
// Complex combination
if (age is > 18 and < 65 or 100) { /* 18-65 or exactly 100 */ }
8. List Patterns (C# 11+)
Match on the structure of arrays and lists:
int[] nums = { 1, 2, 3 };
if (nums is [1, 2, 3]) { /* exact match */ }
if (nums is [1, _, _]) { /* starts with 1 */ }
if (nums is [_, _, 3]) { /* ends with 3 */ }
if (nums is [1, .., 3]) { /* starts 1, ends 3 */ }
if (nums is [.., 2, ..]) { /* contains 2 anywhere */ }
if (nums is []) { /* empty */ }
if (nums is not []) { /* not empty */ }
.. is a slice pattern — [1, .., 3] means "starts with 1, ends with 3, anything in between."
9. Relational Patterns
Compare values directly in patterns:
int temp = 25;
string Feel(int t) => t switch
{
< 0 => "Freezing",
>= 0 and < 15 => "Cold",
>= 15 and < 25 => "Mild",
>= 25 and < 35 => "Warm",
>= 35 => "Hot"
};
10. Practical Examples
Example 1: Parsing Input
object ParseInput(object input) => input switch
{
int n when n > 0 => $"Positive number: {n}",
string s when s.Length > 0 => $"Non-empty string: {s}",
null => "Null input",
_ => "Unknown type"
};
Example 2: State Machine
enum State { Idle, Running, Paused, Stopped }
string HandleCommand(State current, string cmd) => (current, cmd) switch
{
(State.Idle, "start") => "Started",
(State.Running, "pause") => "Paused",
(State.Running, "stop") => "Stopped",
(State.Paused, "resume") => "Resumed",
(State.Paused, "stop") => "Stopped",
(State.Stopped, "reset") => "Reset to idle",
_ => "Invalid command"
};
Example 3: Validation
record User(string Name, int Age, string Email);
bool IsValid(User u) => u switch
{
{ Name: null or "" } => false,
{ Age: < 0 or > 150 } => false,
{ Email: not null and _ } when u.Email.Contains("@") => true,
_ => false
};
11. Pattern Matching vs Traditional
| Aspect | Traditional | Pattern Matching |
|---|---|---|
| Type check + cast | is + (T) | is T t |
| Multi-way branch | switch statement | switch expression |
| Property check | Multiple if conditions | { Prop: value } |
| Null check | if (x != null) | is not null |
| Collection shape | Manual index checks | [1, .., 3] |
12. Common Interview Questions
Q: What is the difference between is and as?
is returns a bool (and with pattern matching, binds a variable). as returns the cast object or null — it never throws, but you still need a null check.
Q: Can pattern matching replace polymorphism?
Sometimes — for simple type-based dispatch. But for complex behavior, polymorphism (virtual methods) is still cleaner. Pattern matching shines for data inspection, not behavior dispatch.
Q: What is the _ (discard) pattern?
A wildcard that matches anything. Used in switch expressions as the default case, or in positional patterns to ignore specific values: (1, _, 3).
Q: Are switch expressions exhaustive?
Yes — the compiler enforces it. If you omit a case, you get a compile error. Use _ as the catch-all.
13. One-Line Interview Answer
Pattern matching in C# is a set of language features that let you inspect and deconstruct data concisely — type patterns (is T t), property patterns ({ Age: > 18 }), positional patterns ((x, y)), list patterns ([1, .., 3]), and switch expressions — reducing boilerplate and making code more readable.