Pattern Matching
Pattern matching unifies type tests, deconstruction, and conditional logic into one expressive feature set.
Introduction
Pattern matching unifies type tests, deconstruction, and conditional logic into one expressive feature set. C# evolved from is checks to recursive patterns, list patterns (C# 11), and switch expressions that rival functional languages while staying imperative-friendly.
Enterprise DTOs, API result types (Success/Failure discriminated unions), and JSON polymorphism all benefit from exhaustive pattern matching. Interviewers at Microsoft-heavy shops expect fluent use of property, positional, and relational patterns.
This lesson ties together is, switch, list patterns, and negation patterns with real domain examples.
The story
A mobile banking app calls a backend API that returns either a success payload with a message or an error with an HTTP-style code. The client must display "OK: Transfer complete" for successes, a friendly "Not found" for 404 errors, and a generic retry prompt for server failures — without a chain of if (result is ApiSuccess) followed by nested type checks.
Pattern matching on a sealed result hierarchy is how modern C# services handle discriminated unions cleanly.
Understanding the topic
Key concepts
- Type pattern: obj is string s binds and casts.
- Constant pattern: x is 42 or case 42:
- Property pattern: p is { Age: >= 18, Country: "US" }
- Positional pattern: Point(0, 0) for deconstruction match.
- List pattern: [first, .. rest] matches collection shape.
- not pattern negates: x is not null.
Step-by-step explanation
- Compiler lowers patterns to type checks and comparisons.
- Switch arms evaluated sequentially — order matters.
- Deconstruction extracts record/tuple components in pattern.
- List patterns use Length and slice .. for rest.
- Exhaustiveness analysis on enums and bool.
- Combine with when for additional guards.
Practical code example
Discriminated API result handling with positional and property patterns:
namespace TechLearningPro.PatternMatching;public abstract record ApiResult;public sealed record ApiSuccess<T>(T Data) : ApiResult;public sealed record ApiError(int Code, string Message) : ApiResult;public static class ResultHandler{public static string Describe(ApiResult result) => result switch{ApiSuccess<string> { Data: var msg } => quot;OK: {msg}",ApiError { Code: 404, Message: var m } => quot;Not found — {m}",ApiError { Code: >= 500 } => "Server error — retry later.",ApiError e => quot;Error {e.Code}: {e.Message}",_ => "Unknown result"};}
Line-by-line code explanation
public abstract record ApiResultis the base type for all API outcomes.ApiSuccess<T>(T Data)represents a successful response carrying typed data.ApiError(int Code, string Message)represents a failure with code and message.Describe(ApiResult result) => result switchpattern-matches on the result type.ApiSuccess<string> { Data: var msg }extracts the message into a variable in the same arm.=> $"OK: {msg}"formats the success string for display.ApiError { Code: 404, Message: var m }matches not-found errors specifically.ApiError { Code: >= 500 }uses a relational pattern for any server error code.ApiError e => ...is the general error arm when no earlier arm matched._ => "Unknown result"handles unexpected types defensively.
Key takeaway: Sealed hierarchy enables exhaustive matching. Property patterns nest inside positional records. Relational >= on Code property.
Real-world use
Where you'll use this in production
- Parsing heterogeneous JSON nodes by shape.
- Validation results with Success/Failure types.
- AST traversal in compilers and rule engines.
- Game entity interaction by type and state.
Best practices
- Use sealed class hierarchies for domain variants.
- Prefer records for positional patterns.
- Order switch arms from specific to general.
- Avoid pattern matching on mutable classes — race conditions.
- Unit test each pattern arm independently.
Common mistakes
- Wrong arm order — general pattern catches before specific.
- Matching mutable object state that changes mid-switch.
- Overly complex nested patterns — extract helpers.
- Ignoring compiler exhaustiveness warnings.
Advanced interview questions
Q1BeginnerType pattern vs cast?
Q2BeginnerProperty pattern syntax?
Q3IntermediateList pattern [..] meaning?
Q4IntermediateImplement Result without exceptions?
Q5AdvancedParse JSON polymorphic array of shapes with pattern matching.
Summary
Pattern matching combines type tests, deconstruction, and conditions. Switch expressions with property/relational patterns replace verbose if chains. Sealed record hierarchies enable safe exhaustive handling. List patterns (C# 11) match collection structure. Next: methods and reusable logic blocks.