Type juggling and implicit coercion (the dark side)
Concept
PHP's type juggling — the automatic conversion of values between types — is one of the most frequently tested interview topics because it contains genuinely surprising behavior that causes real bugs. Understanding it is not about memorizing edge cases; it's about understanding the underlying rules so you can predict behavior you've never seen before.
The type juggling rules
PHP uses type juggling in: loose comparisons (==), arithmetic operations, string interpolation, function calls without strict types, and built-in functions.
String to number conversion: PHP extracts a numeric prefix from the string. "42abc" becomes 42. "abc" becomes 0. "3.14text" becomes 3.14. This is the source of many bugs.
Bool conversions: The following values are false in boolean context; everything else is true:
falseitself0(integer zero)0.0(float zero)""(empty string)"0"(the string "zero" — this is the famous one)[](empty array)null- Unset/undefined variables
Array arithmetic: When PHP needs a number and gets an array, it throws a TypeError (PHP 8.0+). Before PHP 8, arrays in numeric contexts produced E_NOTICE and evaluated to 0 or 1.
The "0" string gotcha
"0" is a non-empty string but is false in boolean context. This means:
if ("0")is falseempty("0")is true(bool) "0"is false
This surprises most developers because "false" (the literal string "false") is true — it's a non-empty, non-"0" string.
Comparison juggling
The == operator applies type juggling before comparing. The most famous case: before PHP 8, 0 == "abc" was true because "abc" juggled to 0. PHP 8 fixed this: comparing an int to a non-numeric string now returns false instead of coercing. But many other juggling behaviors remain.
| Expression | PHP 7 | PHP 8 |
|---|---|---|
0 == "abc" | true | false |
0 == "" | true | false |
0 == "0" | true | true |
"1" == "01" | true | true |
"10" == "1e1" | true | true |
100 == "1e2" | true | true |
null == false | true | true |
null == 0 | true | true |
null == "" | true | true |
null == "0" | false | false |
Code Example
<?php
declare(strict_types=1);
// Type juggling demonstrations — understanding, not just memorizing
// String to integer conversion
var_dump((int) "42abc"); // int(42)
var_dump((int) "abc"); // int(0) — no numeric prefix
var_dump((int) ""); // int(0)
var_dump((int) "0x1A"); // int(0) — hex strings NOT converted by (int)cast
var_dump(intval("0x1A", 16)); // int(26) — use intval with base for hex
// The "0" string gotcha
$userInput = "0"; // e.g., user answered question zero
if ($userInput) {
echo "User answered\n";
} else {
echo "No answer (WRONG — user answered '0'!)\n"; // This runs!
}
// Fix: check explicitly
if ($userInput !== "") {
echo "User answered correctly\n"; // This runs
}
// Arithmetic type juggling
$result = "10" + 5; // int(15) — "10" juggled to 10
$result2 = "10.5" + 5; // float(15.5)
$result3 = "10abc" + 5; // int(15) with E_WARNING about non-numeric value
// Boolean juggling table
$falsy = [false, 0, 0.0, "", "0", [], null];
foreach ($falsy as $val) {
$type = gettype($val);
$repr = var_export($val, true);
echo "(bool) {$repr} ({$type}) = " . var_export((bool) $val, true) . "\n";
}
// PHP 8 vs PHP 7 comparison change
// PHP 8: comparing int 0 to non-numeric string → false
$token = "abc123";
if (0 == $token) {
echo "Match (PHP 7 behavior — dangerous!)\n";
} else {
echo "No match (PHP 8 — correct)\n"; // PHP 8+ runs this
}
// Real-world bug: in_array() with loose comparison (PHP 7 style)
$allowedIds = [1, 2, 3];
var_dump(in_array("abc", $allowedIds)); // PHP 7: true! PHP 8: false
var_dump(in_array("abc", $allowedIds, true)); // Always false — strict mode
// Always use strict in_array:
function isAllowedId(int|string $id, array $allowed): bool
{
return in_array($id, $allowed, strict: true);
}Interview Q&A
Q: What is the most dangerous type juggling behavior in PHP and how did PHP 8 fix it?
The most dangerous was 0 == "any-string" evaluating to true in PHP 7 because non-numeric strings coerced to 0, making 0 == $userToken true for any non-numeric token. This was a real security vulnerability: code like if ($storedHash == $userHash) could allow authentication bypass if $userHash was a string like "0e123456" (a magic hash) compared against a stored hash that was also a string starting with 0e — both evaluated to 0.0 as floats. PHP 8 changed the comparison rules: when comparing an integer to a non-numeric string, PHP now converts the string to a number for the comparison only if the string IS numeric, otherwise it returns false instead of coercing. This eliminated the magic hash vulnerability and made comparisons more predictable.
Q: Why is empty("0") true in PHP when "0" is a non-empty string?
empty() is not a pure emptiness check — it is a combined "undefined or falsy" check. The "0" string is falsy because of a special rule in PHP's truthiness system: the only non-empty string that evaluates to false is "0" (the string representation of zero). PHP's designers made this choice for historical consistency with numeric string comparisons. The practical consequence: never use empty() to validate user input where "0" is a valid value (e.g., a form field where the user types 0). Use isset($var) && $var !== "" or strlen($var) > 0 instead. Alternatively, use !== '' to check for non-empty string regardless of value.
Q: How does PHP handle arithmetic operations on non-numeric strings in PHP 8 vs PHP 7?
In PHP 7, using a non-numeric string in arithmetic ("abc" + 5) would trigger an E_NOTICE and silently return 5 (the string coerced to 0). In PHP 8.0, this was upgraded to E_WARNING. In PHP 8.1, "abc" + 5 still produces a result (5) but throws E_WARNING: A non-numeric value encountered. The operation does not throw a TypeError for arithmetic — only function calls with typed parameters do. For a string with a numeric prefix like "10abc", PHP 8.1 produces E_DEPRECATED and will remove the coercion in a future version. With strict_types=1, none of this changes for arithmetic — strict types only governs function parameter coercion, not arithmetic operations.