switch Expressions & Pattern Matching

C# · Lesson 10 of 31 6 min read Updated

When one value can lead to many different results, a long else if chain gets hard to read. C# offers switch for this, in two forms: the classic switch statement and the modern switch expression. Both are powered by patterns, small descriptions of the shape of a value, such as "greater than 90", "a string" or "a list that ends with 6".

Pattern matching is one of the features that makes modern C# pleasant to write. In this lesson you will start with the classic statement and move step by step to the patterns used in real code today.

The switch statement

A switch statement compares one value against several case labels and runs the matching section. default runs when nothing else matches.

C#
int day = 3;

switch (day)
{
    case 1:
        Console.WriteLine("Monday: maths test");
        break;
    case 2:
    case 3:
        Console.WriteLine("Practical lab");
        break;
    case 6:
    case 7:
        Console.WriteLine("Weekend");
        break;
    default:
        Console.WriteLine("Regular classes");
        break;
}
Output
Practical lab

Each section must end with break (or return or throw). Unlike C and Java, C# never lets one section silently fall through into the next; forgetting break is compile error CS0163. Stacking labels, as with case 2: and case 3:, is how you share one section between several values.

switch expressions

Most of the time you use switch to choose a value. A switch expression (C# 8) does exactly that, with much less noise: the value comes first, then switch, then arms of the form pattern => result, separated by commas.

C#
int marks = 82;
string grade = marks switch
{
    >= 90 => "A+",
    >= 75 => "A",
    >= 60 => "B",
    >= 40 => "C",
    _ => "Needs improvement"
};
Console.WriteLine($"Grade: {grade}");

int month = 7;
string season = month switch
{
    12 or 1 or 2 => "Winter",
    >= 3 and <= 5 => "Summer",
    >= 6 and <= 9 => "Monsoon",
    10 or 11 => "Post-monsoon",
    _ => "Invalid month"
};
Console.WriteLine($"Month {month}: {season}");
Output
Grade: A
Month 7: Monsoon

Several kinds of pattern appear here:

  • Relational patterns such as >= 90 compare the value with a constant.
  • Logical patterns combine patterns with and, or and not. >= 6 and <= 9 means "from 6 to 9".
  • Constant patterns such as 12 match one exact value.
  • The discard pattern _ matches anything, like default.

Arms are tried from top to bottom and the first match wins, just like else if. A switch expression must produce a value for every input. If no arm matches, the program throws a SwitchExpressionException at run time, and the compiler warns you in advance with CS8509 ("the switch expression does not handle all possible values"). Ending with a _ arm is the usual fix.

Type patterns and when guards

Patterns can also test a value's type and give it a name in the same step. A when clause adds an extra condition to an arm.

C#
object?[] values = [42, -7, "Pune", 7.5, true, null];

foreach (object? v in values)
{
    string description = v switch
    {
        int n when n > 0 => $"positive int {n}",
        int n => $"int {n}",
        string s => $"text of length {s.Length}",
        double d => $"double {d}",
        null => "nothing at all",
        _ => $"something else: {v}"
    };
    Console.WriteLine(description);
}
Output
positive int 42
int -7
text of length 4
double 7.5
something else: True
nothing at all

int n is a type pattern with a declaration: if v holds an int, the arm matches and the number is available as n, already converted. The foreach loop simply visits each item in the array; you will learn it in Loops. The same type test works in an if: if (v is string s && s.Length > 3) { ... }.

Property and tuple patterns

A property pattern looks inside an object and matches its properties with { Name: pattern }. A tuple pattern switches on several values at once by putting them in brackets.

C#
DateTime[] dates =
[
    new DateTime(2027, 1, 26),
    new DateTime(2026, 8, 15),
    new DateTime(2026, 11, 14),
    new DateTime(2026, 12, 2)
];

foreach (DateTime d in dates)
{
    string kind = d switch
    {
        { Month: 1, Day: 26 } => "Republic Day",
        { Month: 8, Day: 15 } => "Independence Day",
        { DayOfWeek: DayOfWeek.Saturday or DayOfWeek.Sunday } => "Weekend",
        _ => "Working day"
    };
    Console.WriteLine($"{d:dd MMM yyyy}: {kind}");
}

string coach = "3A";
bool tatkal = true;
int fare = (coach, tatkal) switch
{
    ("SL", false) => 450,
    ("SL", true) => 600,
    ("3A", false) => 1170,
    ("3A", true) => 1500,
    _ => 0
};
Console.WriteLine($"{coach}, tatkal {tatkal}: Rs {fare}");
Output
26 Jan 2027: Republic Day
15 Aug 2026: Independence Day
14 Nov 2026: Weekend
02 Dec 2026: Working day
3A, tatkal True: Rs 1500

15 August 2026 is a Saturday, yet it prints "Independence Day" because that arm comes first. The fares are invented for the example. Tuple patterns are a tidy replacement for nested if statements that check two or three values together.

List patterns

C#
int[][] overs = [[4, 6, 0, 6], [6, 1, 2], [1, 0, 4], []];

foreach (int[] balls in overs)
{
    string summary = balls switch
    {
        [] => "No balls bowled",
        [6, ..] => "Started with a six",
        [.., 6] => "Ended with a six",
        [var first, .., var last] => $"First ball {first}, last ball {last}",
        _ => "Just one ball"
    };
    Console.WriteLine(summary);
}
Output
Ended with a six
Started with a six
First ball 1, last ball 4
No balls bowled

[var first, .., var last] matches any list with at least two elements and names the first and last. The final _ catches what is left: a single-ball list.

Common mistakes

  • Forgetting the _ arm. You get warning CS8509, and an unmatched value crashes at run time with SwitchExpressionException.
  • A broad pattern before a narrow one. If >= 40 comes before >= 90, the later arm can never match, and the compiler reports error CS8510: The pattern has already been handled by a previous arm of the switch expression.
  • Missing break in a switch statement. Error CS0163. Every section needs break, return or throw.
  • Mixing up the syntaxes. Statements use case x: with colons; expressions use x => with commas between arms and a semicolon after the closing brace.
  • Writing >= 6 && <= 9. Inside patterns, use the words and, or and not, not && and ||.

Key points

  • A switch statement runs one section; each section ends with break, and there is no fall-through.
  • A switch expression value switch { pattern => result, ... } returns a value.
  • Patterns include constants, relational (>= 90), logical (and, or, not), type (int n), property ({ Month: 1 }), tuple ((a, b)) and list ([first, .., last]) patterns.
  • when adds an extra condition to an arm, and _ matches anything.
  • Arms are checked in order; the compiler warns about missing cases and rejects unreachable ones.

Exercise

A courier company in Kochi charges by weight and zone. Write a switch expression on the tuple (zone, kg) where zone is a string and kg is an int: "local" costs Rs 40 for up to 1 kg and Rs 70 above that; "state" costs Rs 60 up to 1 kg and Rs 110 above; "national" costs Rs 90 up to 1 kg and Rs 180 above. Anything else costs 0. Print the charge for ("state", 3) and ("national", 1).

Show solution
C#
(string zone, int kg)[] parcels = [("state", 3), ("national", 1)];

foreach (var (zone, kg) in parcels)
{
    int charge = (zone, kg) switch
    {
        ("local", <= 1) => 40,
        ("local", _) => 70,
        ("state", <= 1) => 60,
        ("state", _) => 110,
        ("national", <= 1) => 90,
        ("national", _) => 180,
        _ => 0
    };
    Console.WriteLine($"{zone}, {kg} kg: Rs {charge}");
}
Output
state, 3 kg: Rs 110
national, 1 kg: Rs 90

Each tuple arm combines a constant pattern for the zone with a relational pattern for the weight. The <= 1 arm comes before the _ arm for the same zone, so light parcels match first.

Quick Quiz

1. What does int x = 15; string s = x switch { < 10 => "small", < 20 => "medium", _ => "large" }; set s to?

2. What happens if no arm of a switch expression matches at run time?

3. Which pattern matches an array with at least two elements and captures the last one?

4. In a switch statement, what is required at the end of each case section?

Learn C# with a mentor

Free lessons get you started. Our Programming & Software Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call