skip to content

In PHP 8, what are the differences between a switch statement and a match expression?

level: juniorimportance: must knowfreq 74%

answer

  1. statement vs expression that returns
  2. == vs ===
  3. fall-through needs break; match never falls
  4. no arm matched: UnhandledMatchError
  5. comma-separated conditions, single expression per arm

basics

~20 s

switch is a statement that compares loosely (==) and falls through until a break. match, since PHP 8.0, is an expression that compares strictly (===), never falls through, returns a value and throws UnhandledMatchError when nothing matches.

solid answer

~40 s

`switch` is a **statement**: it compares its subject with each `case` using **loose `==`**, runs from the first matching case onward and keeps **falling through** into later cases until a `break`. With no matching case and no `default`, it silently does nothing. `match`, available since **PHP 8.0**, is an **expression** that evaluates to a value, compares with **strict `===`**, runs exactly one arm with no fall-through, and throws **`UnhandledMatchError`** when nothing matches and there is no `default`. Each `match` arm is a single expression, several conditions share one arm with commas (`2, 3 => 'In transit'`), and conditions are evaluated lazily, top to bottom. Use `match` to map a value to a result, such as a parcel status code to a label; keep `switch` for running several statements per case.

code

php · 25 lines
php
<?php
declare(strict_types=1);

$statusCode = 3;

switch ($statusCode) {            // loose ==, statement
    case 1:
        $label = 'Label created';
        break;
    case 2:
    case 3:                       // 2 and 3 share a body by falling through
        $label = 'In transit';
        break;
    default:
        $label = 'Unknown';
}

$label = match ($statusCode) {    // strict ===, expression
    1 => 'Label created',
    2, 3 => 'In transit',
    4 => 'Delivered',
    default => 'Unknown',
};

echo $label, PHP_EOL;             // In transit

go deeper

for a junior

Know that match returns a value, compares strictly with ===, needs no break and throws UnhandledMatchError when nothing matches.

for a middle

Explain fall-through in switch, lazy top-to-bottom evaluation of match conditions, comma-separated conditions and the match (true) idiom for ranges.

for a senior

Judge where the stricter comparison and exhaustiveness of match catch real bugs, such as unmapped status codes, and where input types must be normalised first.

for a principal

Set a codebase convention on when to use match, switch or polymorphism for branching on codes, weighing exhaustiveness errors against silent defaults.

## Two constructs for the same shape of problem Both `switch` and `match` take a **subject** value and compare it against a list of alternatives. That is where the similarity ends. `switch` is PHP's classic C-style statement; `match`, added in **PHP 8.0**, was designed to remove the traps of `switch` for the common case of "turn this value into that value". ## Side-by-side | Aspect | `switch` | `match` | |---|---|---| | Kind | statement | expression, evaluates to a value | | Comparison | loose `==` | strict `===` | | After a match | falls through to later cases until `break` | runs one arm only | | Nothing matches, no `default` | does nothing | throws `UnhandledMatchError` | | Arm body | any number of statements | a single expression | | Several values, one body | stacked `case 2: case 3:` | `2, 3 =>` | | Available since | always | PHP 8.0 | ## Loose versus strict comparison `switch` compares with `==`, so PHP's type-juggling rules apply. A string `"1"` from a query parameter matches `case 1:`; two numeric strings compare as numbers, so `"1"` even matches `case "01":` first if that case comes earlier. `match` compares with `===`: `"1"` does not match the arm `1 =>`. That makes `match` predictable, and it also means the types of your data must be right before you match. ## Fall-through In a `switch`, execution starts at the matching `case` and continues through every following `case` body until it hits `break` (or `return`, `continue`, `throw`). Forgetting a `break` is a classic bug: status 1 prints "Label created" *and* "In transit". Fall-through is sometimes deliberate, to share one body between cases, but it must be read carefully. `match` has no fall-through at all; shared bodies are written with commas. ## Exhaustiveness A `match` must handle its subject. If no condition is identical to the subject and there is no `default` arm, PHP throws **`UnhandledMatchError`**, a subclass of `Error`, with a message such as `Unhandled match case 5`. A `switch` without a `default` simply skips everything. The error is a feature: a new parcel status that nobody mapped fails loudly instead of producing an empty label. ## Expression semantics Because `match` is an expression, it is assigned or returned directly: ```php $label = match ($statusCode) { 1 => 'Label created', 2, 3 => 'In transit', 4 => 'Delivered', default => 'Unknown', }; ``` A few rules follow from being an expression: - Each arm holds **one expression**. For several steps, call a function, or use a `throw` expression (PHP 8.0 made `throw` an expression) to fail in an arm. - Conditions are evaluated **in order and lazily**: a condition is only evaluated if every earlier one failed, and only the winning arm's result is evaluated. - A `match` used as a statement on its own still needs a terminating semicolon. - Two `default` arms are a compile error. ## Ranges and other non-identity checks For conditions that are not "equals this value", the idiom is `match (true)` with boolean conditions: ```php $band = match (true) { $weightKg < 2 => 'small', $weightKg < 10 => 'medium', default => 'large', }; ``` The first arm whose condition is identical to `true` wins. With `switch (true)` the same trick works but compares loosely, so a truthy non-boolean case expression would also match. ## Choosing between them 1. Mapping a value to a value, such as a status code to a label: **`match`**. 2. Several statements per branch, or deliberate shared fall-through: **`switch`**, or `if`/`elseif`. 3. Input whose type is uncertain (strings from a request or a database): cast it first, then `match`; do not rely on `switch`'s loose comparison to paper over types. A PHP 8.5 detail on the old form: ending a `case` label with a semicolon (`case 1;`) instead of a colon was always accepted and is now deprecated.

  • What happens in PHP when a switch case has no break?
    Execution falls through: after the matching case's statements, PHP keeps running the next case bodies, without testing their labels, until it reaches a `break`, `return`, `continue`, `throw` or the end of the switch. That is useful for deliberately sharing a body, but an accidental missing `break` runs code meant for other values.
  • Can a match arm run several statements?
    Not directly: an arm's right-hand side is a single expression. Put the steps in a function or method and call it from the arm, or use an immediately invoked closure. To fail inside an arm, use a `throw` expression, which PHP 8.0 made legal in expression position.
  • How do you express a range check such as weight below 2 kg with match?
    Use `match (true)` and write boolean conditions as the arms: `$weight < 2 => 'small', $weight < 10 => 'medium', default => 'large'`. The first condition identical to `true` wins. Because the comparison is strict, a condition must evaluate to real `true`, not just a truthy value.

saying these in an interview costs you the question

  • Says match compares with == just like switch does.
  • Believes a switch without a default throws an error when nothing matches.
  • Claims match arms fall through to the next arm without a break.
  • Thinks match is available in PHP 7.4.
  • Says a match expression without default returns null when no arm matches.