C# Switch

A switch lets a C# program choose one path from several possible matches. It is often cleaner than a long if/else if chain when one value, shape, or pattern decides what should happen. Modern C# supports both traditional switch statements and compact switch expressions.

Overview: How Switch Works

A switch compares an input expression against a list of labels or patterns. In a traditional switch statement, C# runs the statements belonging to the first matching case. In a switch expression, C# evaluates to a value from the first matching arm. Both forms are ordered: when several patterns could match, the first matching one wins.

Older C# code often uses switch with simple constants such as strings, integers, chars, or enum values. That style is still useful for menus, command names, status codes, and fixed categories. Modern C# also uses pattern matching in switch: a case can test a type, a relational condition such as >= 90, a property shape, null, or a combination of patterns.

A traditional switch statement does not automatically fall through from one non-empty case to the next. C# requires each reachable case section to end with a control-flow statement such as break, return, throw, or goto case. This is different from C and JavaScript, where accidental fall-through is a common bug. C# still allows multiple labels to share one case body when they are stacked before the statements.

A default label in a statement, or the discard pattern _ in an expression, handles unmatched input. Including a fallback is usually wise unless the compiler can prove all possibilities are covered, such as every member of a small enum in some situations. With a switch expression, missing matches can cause a runtime SwitchExpressionException, so exhaustiveness matters more than it does in a statement that simply does nothing after no case matches.

Under the hood, the C# compiler lowers a switch into branching logic in Intermediate Language. For dense integer cases it may emit a jump table. For strings it may generate efficient equality checks, sometimes with hashing. For pattern matching it emits tests in source order, including type checks, null checks, relational comparisons, and guard conditions. The important practical rule is simple: write cases from most specific to most general, because earlier matches hide later ones.

Syntax

string command = "open";

switch (command)
{
    case "open":
        Console.WriteLine("Opening");
        break;
    case "save":
    case "write":
        Console.WriteLine("Saving");
        break;
    default:
        Console.WriteLine("Unknown command");
        break;
}
Part Meaning
switch (command) Evaluates the input expression once and compares it to the cases.
case "open": Runs when the input matches that constant or pattern.
break Leaves the switch statement after the case body finishes.
default Optional fallback when no other case matches.
case "save": case "write": Multiple labels can share the same statements.

A switch expression has a different shape. It returns a value, uses => arms, separates arms with commas, and usually ends with a discard fallback.

int score = 84;
string grade = score switch
{
    >= 90 => "A",
    >= 80 => "B",
    >= 70 => "C",
    _ => "Review"
};

Examples

Matching Fixed Commands

using System;

class Program
{
    static void Main()
    {
        string command = "save";

        switch (command)
        {
            case "open":
                Console.WriteLine("Opening the document.");
                break;
            case "save":
                Console.WriteLine("Saving the document.");
                break;
            case "close":
                Console.WriteLine("Closing the document.");
                break;
            default:
                Console.WriteLine("Command not recognized.");
                break;
        }

        Console.WriteLine("Menu handled.");
    }
}

Output:

Saving the document.
Menu handled.

The value of command is "save", so only that case body runs. The break exits the switch, and execution continues after the closing brace. The default case is skipped because a matching case was found.

Grouping Cases

using System;

class Program
{
    static void Main()
    {
        char accessLevel = 'E';

        switch (accessLevel)
        {
            case 'A':
                Console.WriteLine("Administrator access");
                break;
            case 'E':
            case 'M':
                Console.WriteLine("Employee access");
                break;
            case 'G':
                Console.WriteLine("Guest access");
                break;
            default:
                Console.WriteLine("Unknown access level");
                break;
        }
    }
}

Output:

Employee access

The labels 'E' and 'M' share one case body. This is the correct way to group equivalent values in a C# switch statement. Because no statements appear between the two labels, there is no illegal fall-through.

Returning a Value with a Switch Expression

using System;

class Program
{
    static void Main()
    {
        int score = 84;

        string grade = score switch
        {
            >= 90 => "A",
            >= 80 => "B",
            >= 70 => "C",
            >= 60 => "D",
            _ => "Needs review"
        };

        Console.WriteLine($"Score: {score}");
        Console.WriteLine($"Grade: {grade}");
    }
}

Output:

Score: 84
Grade: B

This example uses relational patterns. C# checks the arms from top to bottom. 84 does not match >= 90, but it does match >= 80, so the expression produces "B". Later arms are not evaluated.

Patterns and when Guards

using System;

class Program
{
    static void Main()
    {
        decimal orderTotal = 125m;
        bool isMember = true;

        string shipping = orderTotal switch
        {
            >= 100m when isMember => "Free priority shipping",
            >= 100m => "Free standard shipping",
            >= 50m => "Reduced shipping",
            _ => "Standard shipping"
        };

        Console.WriteLine(shipping);
    }
}

Output:

Free priority shipping

The first arm has both a relational pattern and a when guard. The amount must be at least 100m, and isMember must be true. Guards are useful when a match depends on extra state that is not part of the switched value itself.

How Switch Works Step by Step

  1. C# evaluates the expression after switch one time.
  2. The compiler checks each case label or expression arm for valid constants, patterns, and reachable order.
  3. At runtime, cases are tested according to the compiled decision plan. For pattern switches, source order matters when patterns overlap.
  4. In a statement, the matching case section runs until it reaches break, return, throw, or another explicit transfer.
  5. In an expression, the matching arm produces a value whose type must fit the overall expression type.
  6. If no statement case matches, the program continues after the switch. If no expression arm matches, the expression throws at runtime unless a fallback arm exists.

The compiler also performs useful safety checks. It rejects duplicate constant cases, reports unreachable switch expression arms, and prevents accidental fall-through between non-empty statement sections. These checks make switch a strong choice when a fixed set of alternatives is clearer than many separate if statements.

Common Mistakes

Forgetting break in a Switch Statement

string status = "new";

switch (status)
{
    case "new":
        Console.WriteLine("Create ticket");
    case "closed":
        Console.WriteLine("Archive ticket");
        break;
}

This does not compile. C# does not allow execution to fall from a non-empty case into the next case. Add break, return, or another explicit transfer after the first case body.

string status = "new";

switch (status)
{
    case "new":
        Console.WriteLine("Create ticket");
        break;
    case "closed":
        Console.WriteLine("Archive ticket");
        break;
}

Output:

Create ticket

Putting a General Pattern Before a Specific One

int score = 95;

string label = score switch
{
    >= 60 => "Passing",
    >= 90 => "Excellent",
    _ => "Not passing"
};

This does not compile because the >= 90 arm can never be reached. Every value that is at least 90 already matches >= 60. Put the more specific pattern first.

int score = 95;

string label = score switch
{
    >= 90 => "Excellent",
    >= 60 => "Passing",
    _ => "Not passing"
};

Console.WriteLine(label);

Output:

Excellent

Using Switch When If Is Clearer

int age = 20;
bool hasId = true;

if (age >= 18 && hasId)
{
    Console.WriteLine("Entry allowed");
}
else
{
    Console.WriteLine("Entry denied");
}

Output:

Entry allowed

This is not wrong, but it is intentionally shown as an if. When there are only two Boolean outcomes, especially with one combined condition, if is often easier to read than forcing the logic into a switch.

Best Practices

  • Use a switch statement when each case performs actions. Use a switch expression when you need to compute one value.
  • Include default or _ unless you are deliberately allowing unmatched values and have tested that behavior.
  • Order overlapping patterns from most specific to most general.
  • Group equivalent constant labels by stacking labels before one shared body.
  • Prefer enums over magic strings when the set of possible values is controlled by your program.
  • Keep case bodies short. Move large behavior into methods so the decision remains easy to scan.
  • Use when guards for extra conditions, but avoid hiding complicated business rules inside one huge switch.
  • Do not use goto case for normal grouping. Stacked labels are clearer for shared behavior.

Practice Exercises

  1. Create a switch statement for a string role with cases for "admin", "editor", and "viewer". Print a different permission message for each role.
  2. Write a switch expression that converts an int month into "Winter", "Spring", "Summer", or "Fall". Use grouped patterns or logical patterns if you know them.
  3. Build a grading switch expression for scores from 0 to 100. Add a fallback for impossible values such as -1 or 105.

Summary

  • switch chooses a path by matching one expression against cases or patterns.
  • A switch statement runs actions; a switch expression produces a value.
  • C# prevents accidental fall-through between non-empty case sections.
  • default and _ provide fallback behavior for unmatched input.
  • Modern C# switches can use constants, relational patterns, type patterns, property patterns, and when guards.
  • Case order matters when patterns overlap, so put specific cases first.
  • Use switch when it improves clarity over a long chain of if/else if checks.