-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
245: Common C# Interview Questions on Records and Pattern Matching
When you hit the interview stage for a mid-to-senior C# role, the interviewer isn't just checking if you know the syntax for records and pattern matching. They want to see if you understand intent. They want to know why you'd pick a record over a class, or why a switch expression is safer than a chain of if-else blocks. Instead of reciting definitions, let's build a small payment processing dispatcher. This is a classic scenario where these features actually shine.
Modeling Payment Types with Record Hierarchies
In a payment system, we deal with a lot of "Data Transfer Objects" that shouldn't change once they're created. If a payment arrives as a Credit Card transaction, we don't want some random service accidentally changing the card number halfway through the pipeline. This is why I'm using records here. They give us immutability and value-based equality out of the box.
public abstract record Payment(decimal Amount, string Currency);
public record CreditCardPayment(decimal Amount, string Currency, string CardNumber, string Expiry)
: Payment(Amount, Currency);
public record PayPalPayment(decimal Amount, string Currency, string Email)
: Payment(Amount, Currency);
public record CryptoPayment(decimal Amount, string Currency, string WalletAddress)
: Payment(Amount, Currency);
Notice I'm using positional records. It's concise, and it automatically generates the constructor and properties for me. In an interview, if you're asked about the difference between a record and a class, point to this: if I create two PayPalPayment objects with the same email and amount, they are considered equal. With a class, they'd be different objects in memory.
Processing Payments via Switch Expressions
Now we need a way to handle these different types. The old way was a bulky switch statement with case and break everywhere. The modern way is the switch expression. It's cleaner and forces you to think about the return value.
public string GetProcessingMessage(Payment payment) => payment switch
{
CreditCardPayment cc => $"Processing card {cc.CardNumber[..4]}****",
PayPalPayment pp => $"Redirecting to PayPal account {pp.Email}",
CryptoPayment cp => $"Awaiting confirmation from wallet {cp.WalletAddress}"
};
I'm using a "Type Pattern" here. The cc, pp, and cp variables are automatically cast to the specific record type, so I can access CardNumber or Email without any manual casting. It's elegant and type-safe.
The "Missing Case" Gotcha
Here is where I usually trip up when I'm rushing a demo, and it's a great point to bring up in an interview. I just wrote that switch expression, but what happens if someone adds a BankTransferPayment record later? The code will compile, but at runtime, it'll throw a SwitchExpressionException because I didn't handle every possible case.
I initially forgot the "discard" pattern. Let's fix that by adding a default case to ensure the system doesn't just crash when a new payment type is introduced.
public string GetProcessingMessage(Payment payment) => payment switch
{
CreditCardPayment cc => $"Processing card {cc.CardNumber[..4]}****",
PayPalPayment pp => $"Redirecting to PayPal account {pp.Email}",
CryptoPayment cp => $"Awaiting confirmation from wallet {cp.WalletAddress}",
_ => "Unknown payment method. Please contact support." // The safety net
};
The _ is the discard pattern. It tells C#, "if nothing else matched, do this." Interviewers love to see that you're thinking about the "unhappy path."
Refining Logic with Property Patterns
Sometimes, just knowing the type isn't enough. What if we want to flag "high-value" payments for manual review? This is where property patterns come in. We can match on the properties of the record directly inside the switch.
public string GetAuditStatus(Payment payment) => payment switch
{
{ Amount: > 10000 } => "Requires Senior Manager Approval",
CreditCardPayment { Amount: > 1000 } => "Requires Fraud Check",
_ => "Standard Processing"
};
Look at the first line: { Amount: > 10000 }. I don't even care what type of payment it is; if it has an Amount property greater than 10,000, it matches. The second line is a hybrid: it must be a CreditCardPayment AND have an amount over 1,000. This level of expressiveness is exactly why pattern matching is a cornerstone of modern C#.
📋 Practical Task
Exercise: Building a Subscription Tier Validator
You are tasked with creating a subscription management system. Create a record hierarchy for different subscription tiers: FreeTier, ProTier (with a RenewalDate), and EnterpriseTier (with a CompanyId and AccountManager).
Write a method string GetAccessLevel(Subscription sub) that uses a switch expression and pattern matching to return the following:
- For
FreeTier: "Basic Access - Ads Enabled" - For
ProTierwhere theRenewalDateis in the past: "Access Expired - Please Renew" - For
ProTierwhere theRenewalDateis in the future: "Premium Access" - For
EnterpriseTier: "Full Access - Account Managed by [AccountManager Name]" - A default case for any unexpected types: "Invalid Subscription"
Constraint: Ensure you use a property pattern to check the RenewalDate within the ProTier match.
There are no comments for now.