Sooner or later you’re branching on one value with five possible answers. A switch case in C# matches that value against case labels and runs the first section that fits, which reads far better than a stack of else if lines.
In this tutorial, I’ll cover the switch case in C# as I use it on .NET 10, from the classic statement with break to pattern matching and the switch expression. You’ll also see the compiler errors each mistake produces.
C# Switch Case Syntax: switch, case, break and default
A switch statement evaluates its expression once, compares the result with each case label from top to bottom, and runs the statements of the first match. If nothing matches, the default section runs:
int day = 3;
switch (day)
{
case 1:
Console.WriteLine("Monday: team standup");
break;
case 3:
Console.WriteLine("Wednesday: sprint review");
break;
case 5:
Console.WriteLine("Friday: deploy freeze");
break;
default:
Console.WriteLine("No meeting today");
break;
}
Output:
Wednesday: sprint review

You can switch on integers, char, string, bool, enums and, with patterns, any type at all. My guide to data types in C# lists the built-in ones.
Why Doesn’t C# Allow Fall-Through Between Cases?
In C and JavaScript, a missing break silently runs the next case too. C# turns that bug into a compile error. Here’s what happens when I forget the break after case 1:
int plan = 2;
switch (plan)
{
case 1:
Console.WriteLine("Basic plan");
case 2:
Console.WriteLine("Pro plan");
break;
}
Build output:
C:\DotNetDemo\SwitchCase\FallThrough.cs(5,5): error CS0163: Control cannot fall through from one case label ('case 1:') to another
The build failed. Fix the build errors and run again.
Every switch section has to end in a jump: break, return, throw, goto case, goto default, or continue when the switch sits inside a loop like the ones in my while loop in C# guide.
Multiple Case Labels and goto in a C# Switch Statement
Fall-through isn’t allowed, but stacking labels on one section is. When you really want one case to continue into another, say so with goto case or goto default:
foreach (var state in new[] { "WA", "OR", "HI", "TX" })
{
Console.Write($"{state}: ");
switch (state)
{
case "WA":
case "OR":
Console.WriteLine("local, next-day");
break;
case "AK":
case "HI":
Console.Write("air freight, then ");
goto default;
default:
Console.WriteLine("ground, 5 days");
break;
}
}
Output:
WA: local, next-day
OR: local, next-day
HI: air freight, then ground, 5 days
TX: ground, 5 days

Since C# 9 you can also write the stacked labels as one pattern, case "WA" or "OR":. For other ways to test one value against a list, see compare a string with multiple values in C#.
Switch Case with Strings and Enums in C#
Switching on a String
String cases compare with ordinal equality, so they’re case-sensitive: "Yes" doesn’t match case "yes". I normalize user input once with Trim().ToUpperInvariant() and write the labels in uppercase:
string[] replies = ["yes", "Yes", " NO ", "maybe"];
foreach (string raw in replies)
{
switch (raw.Trim().ToUpperInvariant())
{
case "YES":
Console.WriteLine($"'{raw}' -> confirmed");
break;
case "NO":
Console.WriteLine($"'{raw}' -> cancelled");
break;
default:
Console.WriteLine($"'{raw}' -> unknown");
break;
}
}
Output:
'yes' -> confirmed
'Yes' -> confirmed
' NO ' -> cancelled
'maybe' -> unknown
ToUpperInvariant() avoids culture surprises such as the Turkish dotted i.
Switching on an Enum
Enums are the most natural fit for a switch, because the labels are a closed list of named values. You can read more in my enum in C# tutorial:
var status = OrderStatus.Shipped;
switch (status)
{
case OrderStatus.Pending:
Console.WriteLine("Waiting for payment");
break;
case OrderStatus.Shipped:
Console.WriteLine("On its way via UPS");
break;
}
enum OrderStatus { Pending, Shipped }
Output:
On its way via UPS
A switch statement doesn’t force you to cover every member. Add Returned later and this code quietly ignores it; a switch expression would warn you.
Pattern Matching in C# Switch Case Labels
Case labels aren’t limited to constants. You can test the type of the value, compare it with >= or <, and add a when guard for anything a pattern can’t express:
object?[] grades = [97, 54, 81, "B+", null, true];
foreach (object? g in grades)
{
string text;
switch (g)
{
case >= 90: text = "honor roll"; break;
case int s when s < 60: text = $"failing ({s})"; break;
case int s: text = $"passing ({s})"; break;
case string letter: text = $"letter {letter}"; break;
case null: text = "no grade yet"; break;
default: text = $"unsupported {g.GetType().Name}"; break;
}
Console.WriteLine($"{g ?? "null"}: {text}");
}
Output:
97: honor roll
54: failing (54)
81: passing (81)
B+: letter B+
null: no grade yet
True: unsupported Boolean

case int s checks the type and gives you a typed variable in one step, with no cast like the ones in type casting in C#. Order matters: the when s < 60 case must come before the plain case int s.
The C# Switch Expression: => Arms and the _ Discard
When each branch just produces a value, the switch expression (C# 8 and later) is shorter. The input comes first, each arm is pattern => result, and _ catches everything else:
int[] scores = [98, 85, 72, 64, 41];
foreach (int score in scores)
{
string letter = score switch
{
>= 90 => "A",
>= 80 => "B",
>= 70 => "C",
>= 60 => "D",
_ => "F"
};
Console.WriteLine($"{score} -> {letter}");
}
Output:
98 -> A
85 -> B
72 -> C
64 -> D
41 -> F

Arms are checked top to bottom, so >= 80 only sees scores under 90. Pair it with the foreach loop in C# like this and you’ve got a grading report in a dozen lines.
What Happens When No Switch Arm Matches?
Leave out the discard and you get warning CS8509, but it still builds. An unmatched input then throws:
Console.WriteLine(Fee("CA"));
Console.WriteLine(Fee("NY"));
static string Fee(string state) => state switch
{
"CA" => "$12.50",
"TX" => "$9.75"
};
Output:
C:\DotNetDemo\SwitchCase\NoMatch.cs(4,42): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '""' is not covered.
$12.50
Unhandled exception. System.Runtime.CompilerServices.SwitchExpressionException: Non-exhaustive switch expression failed to match its input.
Unmatched value was NY.
at <PrivateImplementationDetails>.ThrowSwitchExpressionException(Object unmatchedValue)
at Program.<<Main>$>g__Fee|0_0(String state) in C:\DotNetDemo\SwitchCase\NoMatch.cs:line 4
at Program.<Main>$(String[] args) in C:\DotNetDemo\SwitchCase\NoMatch.cs:line 2
On .NET (Core 3.0 and later) the exception is SwitchExpressionException; on .NET Framework it’s InvalidOperationException. Don’t wrap it in try-catch; add a _ arm that returns a fallback or throws a clear ArgumentOutOfRangeException.
Property and Tuple Patterns in a C# Switch Expression
A property pattern looks inside an object. This shipping rule checks membership first, then the state, then the order total:
using System.Globalization;
CultureInfo.CurrentCulture = new("en-US");
Order[] orders = [new("Jenna Moore", "CA", 120m, true),
new("Ryan Carter", "TX", 35m, false),
new("Chloe Brady", "AK", 80m, false)];
foreach (var o in orders)
{
decimal ship = o switch
{
{ IsMember: true } => 0m,
{ State: "AK" or "HI" } => 24.99m,
{ Total: >= 50m } => 0m,
_ => 7.99m
};
Console.WriteLine($"{o.Name} ({o.State}): {ship:C}");
}
record Order(string Name, string State, decimal Total, bool IsMember);
Output:
Jenna Moore (CA): $0.00
Ryan Carter (TX): $7.99
Chloe Brady (AK): $24.99

I set en-US because :C follows the machine’s culture. To switch on two values at once, wrap them in a tuple and use positional patterns:
Console.WriteLine(Winner("rock", "scissors"));
Console.WriteLine(Winner("paper", "scissors"));
Console.WriteLine(Winner("rock", "rock"));
static string Winner(string p1, string p2) => (p1, p2) switch
{
var (x, y) when x == y => "tie",
("rock", "scissors") or ("paper", "rock")
or ("scissors", "paper") => "player 1 wins",
_ => "player 2 wins"
};
Output:
player 1 wins
player 2 wins
tie
The when x == y arm catches a tie first. More on tuple in C#.
Combining Switch Patterns with and, or and not
C# 9 added and, or and not for patterns. not binds first, then and, then or, so I add parentheses whenever I mix them:
foreach (char c in "a7Z# ")
{
string kind = c switch
{
(>= 'a' and <= 'z') or (>= 'A' and <= 'Z') => "letter",
>= '0' and <= '9' => "digit",
not ' ' => "symbol",
_ => "space"
};
Console.WriteLine($"'{c}' is a {kind}");
}
Output:
'a' is a letter
'7' is a digit
'Z' is a letter
'#' is a symbol
' ' is a space
The same and/or thinking shows up in a leap year check in C#, where divisibility rules combine.
Common C# Switch Case Compiler Errors
This file packs three common switch mistakes into one build:
int code = 404;
switch (code)
{
case 200: Console.WriteLine("OK"); break;
case 200: Console.WriteLine("Again"); break;
case > 100: Console.WriteLine("Big"); break;
case 404: Console.WriteLine("Not found"); break;
}
string label = code switch
{
_ => "any",
404 => "not found"
};
Build output:
C:\DotNetDemo\SwitchCase\SwitchErrors.cs(6,5): error CS0152: The switch statement contains multiple cases with the label value '200'
C:\DotNetDemo\SwitchCase\SwitchErrors.cs(8,10): error CS8120: The switch case is unreachable. It has already been handled by a previous case or it is impossible to match.
C:\DotNetDemo\SwitchCase\SwitchErrors.cs(14,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match.
The build failed. Fix the build errors and run again.
| Code | What it means | Fix |
|---|---|---|
| CS0163 | A section falls through to the next case | End it with break, return, throw or goto |
| CS0152 | Two cases use the same constant | Delete or merge the duplicate |
| CS8120 | A case is unreachable (an earlier case covers it) | Move the specific case above the general one |
| CS8510 | A switch expression arm is unreachable, often after _ | Keep the discard arm last |
| CS8509 (warning) | A switch expression isn’t exhaustive | Add a _ arm |
Switch vs If-Else in C#: Which Should You Use?
| Situation | My pick |
|---|---|
| One value, several fixed answers | Switch expression |
| One value, several blocks of statements | Switch statement |
| Ranges or types of one value | Switch with relational or type patterns |
| Conditions on several unrelated variables | if / else if |
| Lookups that change at run time | A dictionary |
For two conditions an if is clearer, as in finding the largest of three numbers. Once the labels become data, such as prices loaded from a file, a dictionary in C# beats hard-coded cases.
Want more practice with control flow? These are good next steps:
- Count through ranges with the for loop in C#
- Run a menu at least once with a do-while loop
- Build a four-operation calculator in C#
- C# interview questions for freshers
Frequently Asked Questions
Is break required in a C# switch case?
Every switch section must end with a jump statement. break is the usual one, but return, throw, goto case and goto default also work. The default section needs one too.
Can you switch on a string in C#?
Yes. String cases use ordinal, case-sensitive comparison, so normalize the input first with something like Trim().ToUpperInvariant() and write the labels to match.
What is the difference between a switch statement and a switch expression?
A switch statement runs blocks of statements; a switch expression returns a value from the first matching arm and has no break or case keywords. See the switch expression reference on Microsoft Learn.
How do you use multiple values in one switch case?
Stack the labels (case "WA": case "OR":) over one section, or since C# 9 combine them with an or pattern: case "WA" or "OR":.
Can a C# switch case check a range like >= 90?
Yes, with relational patterns from C# 9: case >= 90: in a statement or >= 90 => "A" in an expression. Combine bounds with and, for example >= 80 and < 90. Details are in the patterns reference on Microsoft Learn.
Where should the default case go?
Anywhere in the switch statement. It runs only when no other case matches, whatever its position, but most teams keep it last for readability.
Bijay Kumar is a renowned software engineer, accomplished author, and distinguished Microsoft Most Valuable Professional (MVP) specializing in SharePoint. With a rich professional background spanning over 15 years, Bijay has established himself as an authority in the field of information technology. He possesses unparalleled expertise in multiple programming languages and technologies such as ASP.NET, ASP.NET MVC, C#.NET, and SharePoint, which has enabled him to develop innovative and cutting-edge solutions for clients across the globe. Read more…