PHP Switch
The switch statement is PHP’s tool for comparing one value against many possible cases without writing a long chain of if/elseif blocks. It evaluates a single expression once, then jumps to the matching case label and runs the code there. Once you understand how PHP compares values and how execution flows between cases, switch becomes one of the clearest ways to express “do one of these several things” logic.
Overview: How switch Works
A switch statement takes one expression and compares it, in order, against a list of case values using a loose equality comparison — the same rules as the == operator (not the strict ===). The very first case whose value loosely equals the switch expression “wins”, and execution begins at that point.
This is the detail that surprises the most beginners: switch does not re-evaluate the condition for every case the way an if/elseif chain does. It computes the switch expression exactly once, then walks the case labels top to bottom looking for the first loose match. Internally, the Zend Engine (PHP’s runtime) compiles the case labels into a sequence of comparison opcodes; when every case is a plain integer or string literal, the engine can optimize this into a hash-based jump table instead of comparing one by one, but the observable behavior is identical to “check cases from top to bottom, use the first match”.
After a match is found, PHP does not automatically stop after that case’s statements. It keeps executing every statement it encounters — including the code inside the next case blocks — until it hits a break, return, continue, throw, or the closing brace of the switch. This behavior is called fallthrough, and it is both switch’s most powerful feature (for intentionally grouping cases) and its most common source of bugs (when a break is forgotten).
Syntax
<?php
$expression = 2;
switch ($expression) {
case 1:
echo "Matched 1";
break;
case 2:
echo "Matched 2";
break;
default:
echo "No match";
}
Output:
Matched 2
| Part | Purpose |
|---|---|
switch (expression) |
The value evaluated exactly once and compared against each case. |
case value: |
A label PHP compares against the switch expression using loose (==) equality. |
break; |
Stops fallthrough and exits the switch immediately. |
default: |
Runs when no case matches. It can appear anywhere in the switch, but is only reached if nothing else matched. |
PHP also supports an alternative colon syntax, useful when a switch is embedded in a template mixed with HTML:
<?php
$isLoggedIn = true;
switch ($isLoggedIn):
case true:
echo "Logged in";
break;
case false:
echo "Logged out";
break;
endswitch;
Output:
Logged in
Examples
Example 1: Basic switch on an integer
<?php
$dayNumber = 3;
switch ($dayNumber) {
case 1:
echo "Monday";
break;
case 2:
echo "Tuesday";
break;
case 3:
echo "Wednesday";
break;
case 4:
echo "Thursday";
break;
case 5:
echo "Friday";
break;
default:
echo "Weekend or invalid day";
}
Output:
Wednesday
PHP checks $dayNumber == 1, then == 2, and so on, stopping at the first match (3). Every branch ends in break, so only one message ever prints.
Example 2: Intentional fallthrough to group cases
<?php
function seasonFromMonth(int $month): string
{
switch ($month) {
case 12:
case 1:
case 2:
return "Winter";
case 3:
case 4:
case 5:
return "Spring";
case 6:
case 7:
case 8:
return "Summer";
case 9:
case 10:
case 11:
return "Autumn";
default:
return "Invalid month";
}
}
echo seasonFromMonth(7) . PHP_EOL;
echo seasonFromMonth(12) . PHP_EOL;
echo seasonFromMonth(13) . PHP_EOL;
Output:
Summer
Winter
Invalid month
Stacking case 12:, case 1:, and case 2: with no statements between them is deliberate fallthrough: any of those three values runs the same return "Winter"; line. Because each branch uses return, no break is even needed — returning from the function exits the switch too.
Example 3: switch(true) for range matching
<?php
function describeStatus(int $code): string
{
switch (true) {
case $code >= 200 && $code < 300:
$result = "Success";
break;
case $code >= 300 && $code < 400:
$result = "Redirection";
break;
case $code >= 400 && $code < 500:
$result = "Client Error";
break;
case $code >= 500 && $code < 600:
$result = "Server Error";
break;
default:
$result = "Unknown Status";
}
return "$code: $result";
}
$codes = [200, 301, 404, 500, 999];
foreach ($codes as $code) {
echo describeStatus($code) . PHP_EOL;
}
Output:
200: Success
301: Redirection
404: Client Error
500: Server Error
999: Unknown Status
A plain switch can only test equality, not ranges. The switch (true) trick works around that: each case is itself a boolean expression, and since the switch value is literally true, the first case that evaluates to true is the one that matches. This reads almost like a cleaner if/elseif chain while keeping switch’s structure.
Under the Hood: Step by Step
When PHP executes a switch, it performs these steps in order:
- Evaluate the switch expression exactly once and store the result.
- Walk the
caselabels from top to bottom in source order (their order matters — it is not sorted or hashed by value in a way that changes results). - For each case, evaluate the case’s expression and compare it to the stored value with loose (
==) equality. - On the first match, jump execution to that case’s first statement.
- Keep executing statements — including sliding into the next case’s body — until a
break,return,continue, orthrowis reached, or the switch simply ends. - If nothing matched, jump to
defaultif one exists (regardless of where it’s written in the source); otherwise do nothing.
One subtle gotcha involves continue. Because PHP treats switch as a loop-like structure for branching purposes, a bare continue inside a switch behaves exactly like break — it only exits the switch. To continue an enclosing loop from inside a switch, you must use continue 2:
<?php
for ($i = 1; $i <= 3; $i++) {
switch ($i) {
case 2:
continue 2;
default:
echo "Number: $i" . PHP_EOL;
}
}
Output:
Number: 1
Number: 3
When $i is 2, continue 2 skips straight to the for loop’s increment step, bypassing the default case entirely — that’s why 2 never prints.
Common Mistakes
Mistake 1: Forgetting break causes unintended fallthrough
<?php
$role = "editor";
switch ($role) {
case "admin":
echo "Full access granted" . PHP_EOL;
case "editor":
echo "Edit access granted" . PHP_EOL;
case "viewer":
echo "Read-only access granted" . PHP_EOL;
break;
default:
echo "No access" . PHP_EOL;
}
Output:
Edit access granted
Read-only access granted
The "editor" case is missing a break, so after printing its own message execution slides straight into the "viewer" case and prints that too — even though the role is not “viewer”. Add the missing break:
<?php
$role = "editor";
switch ($role) {
case "admin":
echo "Full access granted" . PHP_EOL;
break;
case "editor":
echo "Edit access granted" . PHP_EOL;
break;
case "viewer":
echo "Read-only access granted" . PHP_EOL;
break;
default:
echo "No access" . PHP_EOL;
}
Output:
Edit access granted
Mistake 2: Using boolean case expressions against the wrong switch value
<?php
$score = 0;
switch ($score) {
case $score >= 90:
echo "Grade: A";
break;
case $score >= 80:
echo "Grade: B";
break;
case $score >= 70:
echo "Grade: C";
break;
default:
echo "Grade: F";
}
Output:
Grade: A
This looks like it tests ranges, but it doesn’t — the switch expression is $score, an integer, not true. Each case value ($score >= 90, etc.) evaluates to a boolean, and comparing an int to a bool converts the int to a bool first. Since $score is 0, (bool) 0 is false, and the very first case (0 >= 90, which is also false) matches by accident, wrongly reporting an A. The fix is to switch on true itself, as shown earlier:
<?php
$score = 0;
switch (true) {
case $score >= 90:
echo "Grade: A";
break;
case $score >= 80:
echo "Grade: B";
break;
case $score >= 70:
echo "Grade: C";
break;
default:
echo "Grade: F";
}
Output:
Grade: F
Best Practices
- Always end each case with
break,return,continue N, orthrowunless fallthrough is deliberate — and if it is, add a short// no breakcomment so the next reader knows it’s intentional. - Always include a
defaultcase, even if it just throws an exception for an unexpected value — silent “nothing happens” bugs are hard to trace. - Remember comparisons are loose (
==), not strict. Be extra careful mixing types across cases (strings, integers, booleans). - Use
switch (true)sparingly for range checks; if the conditions get complex, anif/elseifchain or PHP 8’smatchexpression is often clearer. - For PHP 8+, prefer
matchwhen every branch is a single expression you want to return or assign — it uses strict comparison and has no fallthrough, which eliminates both mistakes shown above. Reach forswitchwhen branches need multiple statements or intentional case grouping. - Group related cases through fallthrough instead of duplicating the same code in several branches.
- Keep each case body short; move nontrivial logic into a dedicated function so the switch itself stays easy to scan.
Practice Exercises
- Write a function
trafficLightAction(string $color): stringthat usesswitchto return"Stop"for"red","Slow down"for"yellow","Go"for"green", and"Unknown color"for anything else. - Write a script that takes an integer score from 0 to 100 and uses
switch (true)to echo a letter grade: A (90+), B (80-89), C (70-79), or F (below 70). Test it with a score of0to make sure it doesn’t fall into the boolean-comparison trap from this lesson. - Rewrite the
seasonFromMonth()example from this lesson using anif/elseifchain instead of fallthrough grouping, then compare which version is easier to read.
Summary
switchevaluates its expression once and compares it against eachcaseusing loose (==) equality, top to bottom.- Without a
break, execution falls through into the next case’s statements — useful when intentional, a bug when accidental. defaultruns when nothing else matches, no matter where it appears in the switch.- The
switch (true)pattern lets you match ranges or complex boolean conditions, but never switch on a real value while writing boolean case expressions. continueinside a switch only exits the switch; usecontinue 2to continue an enclosing loop.- For simple value-to-value mappings without fallthrough, consider PHP 8’s
matchexpression instead.
