The match Expression

Reviewed & published by Brayan K

PHP 8's match is a sharper, safer cousin of switch: it returns a value, compares strictly, never falls through, and fails loudly on the unexpected. This lesson shows when to reach for it — and the elegant match(true) trick for ranges.

Part of the free PHP course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ The Basics

match takes a subject and a set of arms written value => result. It returns the result of the first arm whose value matches, and because it's an expression, you can assign or echo that result directly. One arm can list several comma-separated values.

<?php
// match (PHP 8) returns a VALUE based on the first matching arm.
// Syntax: match (subject) { value => result, ..., default => result };

$status = 404;

$message = match ($status) {
    200 => "OK",
    301, 302 => "Redirect",       // one arm can list several values
    404 => "Not Found",
    500 => "Server Error",
    default => "Unknown",
};

echo $message . "\n";   // Not Found

// match is an EXPRESSION, so it produces a value you can assign or echo.
echo match (3) { 1 => "one", 2 => "two", 3 => "three" } . "\n"; // three
?>

2️⃣ Strict Matching (=== not ==)

The headline safety feature: match compares with strict ===, whereas switch uses loose ==. So a string "1" will not match an integer 1 arm — exactly the type-juggling surprise that switch lets slip through.

<?php
// match compares with === (STRICT). switch uses == (loose).
// This avoids a whole class of juggling bugs.

$value = "1";   // a STRING

$switchResult = "(switch)";
switch ($value) {
    case 1:                       // loose: "1" == 1 is TRUE
        $switchResult = "matched int 1";
        break;
    default:
        $switchResult = "default";
}

$matchResult = match ($value) {
    1 => "matched int 1",         // strict: "1" === 1 is FALSE -> skipped
    "1" => "matched string 1",    // this one wins
    default => "default",
};

echo $switchResult . "\n";   // matched int 1   (loose surprise!)
echo $matchResult . "\n";    // matched string 1 (strict, predictable)
?>

3️⃣ No Fall-Through, No break

In a switch, forgetting break makes execution "fall through" into the next case — a perennial source of bugs. match arms are self-contained: exactly one runs, and there's no break to forget.

<?php
// No fall-through, no break needed. Each arm is self-contained.
// switch needs 'break' or it keeps running into the next case.

$role = "editor";

$level = match ($role) {
    "admin"  => 3,
    "editor" => 2,
    "viewer" => 1,
    default  => 0,
};
echo "Access level: $level\n";   // Access level: 2

// Forgetting 'break' in switch causes accidental fall-through bugs —
// match simply cannot have that problem.
?>

4️⃣ match(true) for Ranges

Here's the trick that makes match shine. Use match (true) and make each arm a boolean condition; the first one that's true wins. A long if/elseif ladder collapses into a clean, readable table — perfect for grades, price bands, and thresholds.

<?php
// match(true) is a clean way to express RANGE / condition logic.
// Each arm is a boolean; the first true arm wins.

$score = 82;

$grade = match (true) {
    $score >= 90 => "A",
    $score >= 80 => "B",
    $score >= 70 => "C",
    $score >= 60 => "D",
    default       => "F",
};
echo "Grade: $grade\n";   // Grade: B

// This replaces a long if/elseif ladder with a tidy table.
$temp = 31;
echo match (true) {
    $temp >= 30 => "hot\n",
    $temp >= 15 => "mild\n",
    default     => "cold\n",
};   // hot
?>

5️⃣ The No-Match Case

If nothing matches and there's no default, match throws an UnhandledMatchError. That's a feature: unexpected values fail loudly during testing instead of slipping by. Add a default arm when you want a catch-all, or omit it when you want every case handled explicitly.

<?php
// If NO arm matches and there's NO default, match throws
// UnhandledMatchError — a feature: unexpected values fail loudly.

function planLabel(string $plan): string {
    return match ($plan) {
        "free" => "Free tier",
        "pro"  => "Pro plan",
        // no default on purpose
    };
}

echo planLabel("pro") . "\n";   // Pro plan

try {
    echo planLabel("enterprise");      // not handled!
} catch (\UnhandledMatchError $e) {
    echo "Caught: unknown plan\n";    // Caught: unknown plan
}
?>

Now you try. Fill the ___ blanks to map an HTTP method to an action.

<?php
// 🎯 YOUR TURN — fill each ___ to map an HTTP method to an action.
$method = "POST";

$action = match ($method) {
    "GET"  => "read",
    "POST" => ___,           // 👉 "create"
    "PUT", "PATCH" => "update",   // 👈 two values share one arm
    "DELETE" => "delete",
    ___ => "unknown",        // 👉 the default arm keyword
};

echo $action . "\n";        // create

// ✅ Expected output:
//    create
?>

🧩 Reorder Challenge

These scrambled lines should map a status code to a label and print it. Put them in the order that prints Status: Not Found.

Define the subject $code first, then open the match, list the arms (each ending in a comma, including default), close it with };, and finally echo the result. Because match is an expression, the closing }; ends the assignment statement. Output: Status: Not Found.

🧠 Quick Recall

Predict the output before revealing each answer.

Prints str. match uses strict ===, so the string "1" does not match the integer 1 arm — it matches the "1" arm.

Throws an UnhandledMatchError. This is deliberate — unexpected values fail loudly rather than passing silently.

Returns C. The first arm ($n >= 90) is false; the second ($n >= 70) is the first true arm, so it wins.

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — match vs switch

Aspectmatchswitch
Returns a value?Yes (expression)No (statement)
Comparison=== (strict)== (loose)
Fall-throughNoYes (needs break)
No matchThrows errorDoes nothing
Multiple values1, 2 => ...stacked case labels
Multi-statement armsNoYes

Mini-Challenge: Shipping Cost

Write it yourself, run it on onecompiler.com/php or your own machine, then check against the expected output in the comments.

<?php
// 🎯 MINI-CHALLENGE: a shipping-cost calculator with match(true).
// No code filled in — follow the steps, then run and compare.
//
// 1. $weight = 7.5  (kg).
// 2. Use match (true) to set $cost:
//       weight <= 1   -> 3.00
//       weight <= 5   -> 6.00
//       weight <= 10  -> 9.00
//       default       -> 15.00
// 3. echo "Shipping: $" . number_format($cost, 2);
//
// ✅ Expected output:
//    Shipping: $9.00

// your code here
?>

🎉 Lesson Complete!

Practice quiz

What does this print? echo match("1") { 1 => "int", "1" => "str", default => "def" };

  • int
  • def
  • str
  • UnhandledMatchError

Answer: str. match compares with strict ===, so the string "1" does not equal the integer 1 arm — it matches the "1" arm and prints str.

Which comparison operator does the match expression use?

  • === (strict)
  • == (loose)
  • <=> (spaceship)
  • It depends on the values

Answer: === (strict). match always uses strict === comparison, unlike switch which uses loose ==. This avoids type-juggling surprises.

What happens when no arm matches and there is no default arm?

  • Returns null
  • Returns false
  • Silently does nothing
  • Throws UnhandledMatchError

Answer: Throws UnhandledMatchError. An unmatched match with no default throws an UnhandledMatchError — a feature, so unexpected values fail loudly.

With $n = 75, what does match(true) { $n >= 90 => "A", $n >= 70 => "C", default => "F" } return?

  • A
  • C
  • F
  • It throws an error

Answer: C. In match(true) the first arm that evaluates to true wins. $n >= 90 is false, $n >= 70 is the first true arm, so it returns C.

Is match a statement or an expression?

  • An expression that returns a value
  • A statement, like switch
  • Neither — it is a loop
  • A function call

Answer: An expression that returns a value. match is an expression: it evaluates to a single value you can assign or echo directly.

Do match arms fall through like switch cases, requiring break?

  • Yes, you need break in each arm
  • Only the default arm falls through
  • No — exactly one arm runs and there is no break
  • Only if you omit default

Answer: No — exactly one arm runs and there is no break. match has no fall-through. Each arm is self-contained, exactly one runs, and there is no break to forget.

How do you make one match arm handle several subject values?

  • Stack them like case labels
  • List them comma-separated: 301, 302 => "Redirect"
  • Use the || operator
  • You cannot — one value per arm

Answer: List them comma-separated: 301, 302 => "Redirect". An arm can list several comma-separated values, e.g. 301, 302 => "Redirect".

Why does switch matching "1" against case 1 succeed where match would not?

  • switch sorts its cases first
  • switch caches results
  • switch only compares strings
  • switch uses loose == comparison

Answer: switch uses loose == comparison. switch uses loose ==, so "1" == 1 is true. match uses strict ===, so the string would not hit an integer arm.

Can a single match arm run multiple statements?

  • Yes, separate them with semicolons
  • No — an arm returns a single expression
  • Yes, wrap them in braces
  • Only inside the default arm

Answer: No — an arm returns a single expression. match arms return a single expression. For multiple statements, call a function from the arm or use switch/if.

What is the idiomatic use of match(true)?

  • To always return the default
  • To loop over an array
  • To replace if/elseif ladders for ranges and conditions
  • To compare two booleans

Answer: To replace if/elseif ladders for ranges and conditions. match(true) makes each arm a boolean condition; the first true arm wins — perfect for grades, price bands, and thresholds.

Continue this course

Frequently asked questions

What is the match expression in PHP?

match is an expression introduced in PHP 8 that returns a value based on the first arm whose condition matches the subject. You write match (subject) { value => result, ... default => result }. Unlike switch, which is a statement that executes code, match evaluates to a single value you can assign or echo. It compares strictly with ===, has no fall-through (so no break statements), allows several comma-separated values per arm, and throws if nothing matches and there's no default.

How is match different from switch?

Four big differences. First, match uses strict === comparison while switch uses loose ==, so match avoids type-juggling surprises. Second, match is an expression that returns a value, whereas switch is a statement. Third, match has no fall-through and needs no break — each arm is self-contained. Fourth, an unmatched value with no default throws an UnhandledMatchError in match, but switch silently does nothing. In modern code, prefer match for value-mapping and reserve switch for cases where you genuinely want to run several statements.

What does match(true) do?

match(true) is a popular idiom for range or condition logic. Because the subject is the boolean true, each arm is itself a boolean expression, and the first arm that evaluates to true wins. This turns a long if/elseif ladder into a compact, readable table — ideal for grading scores, banding prices, or classifying temperatures. For example match (true) { $score >= 90 => 'A', $score >= 80 => 'B', default => 'F' }.

What happens if no arm matches in a match expression?

If no arm matches the subject and you haven't provided a default arm, match throws an UnhandledMatchError. This is intentional and useful: an unexpected value fails loudly and immediately rather than slipping through unnoticed, which is exactly what you want for things like enum-like values where every case should be handled. If you want a catch-all instead, add a default => ... arm; if you want strictness, omit default so new, unhandled values surface during testing.

Related lessons