Himanshu SharmaSoftware engineer
Let’s talk

PHP · Lesson 14 of 54

switch and match

Choose between many values with switch and the PHP 8 match expression, learn why break matters in switch, and why match is stricter and safer.

11 minControl flowUpdated 22 October 2026

Awaiting technical review. This lesson is readable but not yet listed in search engines.

By the end of this lessonReplace long if chains with switch or match, and explain how the two differ.

One value, many possibilities

When you compare the same variable against a list of values, a long if / elseif chain repeats itself:

php
<?php
if ($status === 'pending') {
    $label = 'Waiting for payment';
} elseif ($status === 'paid') {
    $label = 'Preparing your order';
} elseif ($status === 'shipped') {
    $label = 'On its way';
} // ...and so on

PHP has two constructs for this situation: the classic switch statement and the newer match expression from PHP 8.

switch

A switch takes one value and jumps to the case that equals it. Each case ends with break, which leaves the switch. default runs when no case matches:

php
<?php
$status = "shipped";

switch ($status) {
    case "pending":
        echo "Waiting for payment\n";
        break;
    case "paid":
        echo "Preparing your order\n";
        break;
    case "shipped":
        echo "On its way\n";
        break;
    default:
        echo "Unknown status\n";
}
result
On its way

Fall-through

The break is easy to forget, and forgetting it changes the meaning. Without break, PHP keeps going into the next case, whatever its label:

php
<?php
$status = "paid";

switch ($status) {
    case "paid":
        echo "Preparing your order\n";
    case "shipped":
        echo "On its way\n";
        break;
    default:
        echo "Unknown status\n";
}
result
Preparing your order
On its way

That is usually a bug. Sometimes it is used on purpose to give several values the same code, by stacking empty cases:

php
<?php
$day = "Sun";

switch ($day) {
    case "Sat":
    case "Sun":
        echo "Weekend: open 10 to 4\n";
        break;
    default:
        echo "Weekday: open 9 to 6\n";
}
result
Weekend: open 10 to 4

switch compares loosely

switch uses ==, so the type juggling described in Comparisons, == versus === and type juggling applies:

php
<?php
$quantity = "1";

switch ($quantity) {
    case 1:
        echo "Matched the integer 1\n";
        break;
    default:
        echo "No match\n";
}
result
Matched the integer 1

match

PHP 8 introduced match, which fixes switch's three weak spots. It is an expression, so it returns a value; it compares with ===; and it never falls through.

php
<?php
$status = "shipped";

$label = match ($status) {
    "pending" => "Waiting for payment",
    "paid" => "Preparing your order",
    "shipped" => "On its way",
    "delivered" => "Delivered",
    default => "Unknown status",
};

echo $label . "\n";
result
On its way

Each arm is value => result, separated by commas. Several values can share an arm:

php
<?php
function openingHours(string $day): string
{
    return match ($day) {
        "Sat", "Sun" => "10 to 4",
        "Mon", "Tue", "Wed", "Thu", "Fri" => "9 to 6",
    };
}

echo openingHours("Sun") . "\n";
echo openingHours("Wed") . "\n";
result
10 to 4
9 to 6

No match is an error

match must produce a value. If nothing matches and there is no default arm, it throws an UnhandledMatchError. That sounds harsh, but it turns a silent bug (an unexpected value slipping through) into an error you notice:

php
<?php
$day = "Holiday";

echo match ($day) {
    "Sat", "Sun" => "Weekend",
    "Mon", "Tue", "Wed", "Thu", "Fri" => "Weekday",
};
result
Fatal error: Uncaught UnhandledMatchError: Unhandled match case 'Holiday' in /path/to/script.php:4
Stack trace:
#0 {main}
  thrown in /path/to/script.php on line 4

Strict comparison

Because match uses ===, the string "1" does not match the integer 1:

php
<?php
$quantity = "1";

echo match ($quantity) {
    1 => "integer one",
    "1" => "string one",
} . "\n";
result
string one

match(true) for ranges

match compares values, but you can test a list of conditions by matching against true. The first arm whose condition is true wins:

php
<?php
function band(int $score): string
{
    return match (true) {
        $score >= 90 => "A",
        $score >= 75 => "B",
        $score >= 50 => "C",
        default => "Fail",
    };
}

echo band(82) . " " . band(91) . " " . band(30) . "\n";
result
B A Fail

Which one to use

Prefer match when you are turning one value into another, which covers most cases: labels, prices per plan, CSS classes per status. Use switch (or plain if) when each case needs several statements, because a match arm holds a single expression. For a fixed set of statuses, enums combine nicely with match.

A useful test: if you are about to write $label = ... in every case of a switch, rewrite it as one match assigned to $label. The result is shorter, every arm is visibly a value, and an unexpected input can no longer slip through unnoticed, because it either reaches default or throws an error you will see straight away.

Common mistakes

  • Forgetting break in a switch, so the next case runs too.
  • Expecting switch to compare strictly. It uses ==.
  • Leaving out default in a match when unexpected values are possible, which throws an error.
  • Trying to put several statements in a match arm. Call a function from the arm instead.

Exercise

Write a function planPrice(string $plan): int with match that returns 0 for "free", 499 for "pro" and 1999 for "team", and throws an error for anything else. Then print the price of "pro" and "team".

Show one possible answer
php
<?php
function planPrice(string $plan): int
{
    return match ($plan) {
        "free" => 0,
        "pro" => 499,
        "team" => 1999,
    };
}

echo planPrice("pro") . "\n";
echo planPrice("team") . "\n";
result
499
1999

Leaving out default is deliberate here: an unknown plan name should fail loudly rather than quietly cost nothing.

Key points

  • switch compares one value against several case labels with ==, and needs break to stop falling through.
  • match is an expression that returns a value, compares with ===, and never falls through.
  • match throws an UnhandledMatchError when no arm matches and there is no default.
  • match(true) is a tidy way to test a list of conditions in order.

Check yourself

1. What happens in a switch when a case has no break?
2. How does match compare its subject with each arm?
3. What does match do if no arm matches and there is no default?
4. Which statement about match is true?