PHP Type Juggling

sa sahaya jerish Updated 01 Nov 2026
12 min read ·Lesson 220 of 220
In this post

What Is Type Juggling? In simple terms: Type juggling is PHP automatically converting a value from one type to another when an operation needs a different type. You do not write a cast — PHP decides o

What Is Type Juggling?

In simple terms: Type juggling is PHP automatically converting a value from one type to another when an operation needs a different type. You do not write a cast — PHP decides on its own.

Technically: PHP does not require explicit type declarations for variables. When an operator, a function, or a language construct expects a specific type, PHP coerces (converts) the operand to that type according to fixed rules. The conversion produces a temporary value; the variable itself keeps its original type.

Why PHP Does This

PHP was designed for the web, where almost all input arrives as text. Automatic conversion lets simple code work without ceremony:

<?php
$quantity = "3";   // from a form, always a string
$price = 250;

echo $quantity * $price;

Output

750

The string "3" is converted to the number 3 for multiplication. This convenience is the reason type juggling exists. The cost is that the rules sometimes produce surprising results, especially in comparisons. Knowing where juggling happens — and how PHP 8 changed it — prevents those surprises.

Juggling vs Casting

Type JugglingType Casting
Who decidesPHP, automaticallyYou, explicitly
SyntaxNone — happens inside operations(int) $x, (string) $x, …
Visibility in codeHiddenObvious
Covered inThis lessonPHP Casting

Where Type Juggling Happens

ContextWhat PHP Converts ToExample
Arithmetic operators (+ - * / % **)Numbers"5" + 3 → 8
String operator (.)Strings5 . 3 → "53"
Conditions (if, while, !, &&, ||, ?:)Booleansif ("0") → false
Loose comparison (==, !=, <, >, <=>)A common type, by comparison rules"10" == "1e1" → true
Array keysInteger or string$a["8"] uses key 8
Function arguments and returns (non-strict mode)The declared scalar type"21" passed to int $n → 21

The following sections cover each context.


Numeric Context: Arithmetic

When a string is used with an arithmetic operator, PHP converts it to a number.

Example

<?php
var_dump("5" + "3");
var_dump("2.5" * 2);
var_dump("10" / "4");
var_dump(" 42" + 0);
var_dump("1e3" + 1);
var_dump(true + true);
var_dump(null + 5);

Output

int(8)
float(5)
float(2.5)
int(42)
float(1001)
int(2)
int(5)

How the Result Type Is Chosen

  • If the string looks like an integer ("5", " 42"), it becomes an int.
  • If it contains a decimal point or exponent ("2.5", "1e3"), it becomes a float.
  • true becomes 1, false and null become 0.
  • Division follows its usual rule: an exact integer result stays int, otherwise float.

Strings That Are Not Fully Numeric

PHP 8 classifies strings into three groups, and arithmetic treats each differently:

StringCategoryArithmetic Behavior (PHP 8)
"42", " 42", "42 ", "4.2e1"Numeric (leading and trailing whitespace allowed)Converted silently
"42 apples"Leading-numericUses 42, raises Warning: A non-numeric value encountered
"apples", ""Non-numericThrows a TypeError
<?php
var_dump("42 apples" + 1);

Output

PHP Warning:  A non-numeric value encountered in /path/script.php on line 2
int(43)
<?php
try {
    var_dump("apples" + 1);
} catch (TypeError $e) {
    echo "TypeError: " . $e->getMessage() . "\n";
}

Output

TypeError: Unsupported operand types: string + int

Version note: In PHP 7, "apples" + 1 produced only a warning and the result 1. PHP 8 made these rules stricter ("Saner numeric strings"), so calculations with clearly invalid data now fail loudly instead of producing wrong numbers.


String Context: Concatenation

The . operator converts both operands to strings:

<?php
var_dump(5 . 3);
var_dump("Total: " . 99.50);
var_dump("Active: " . true);
var_dump("Deleted: " . false);

Output

string(2) "53"
string(11) "Total: 99.5"
string(9) "Active: 1"
string(9) "Deleted: "

99.50 loses its trailing zero, true becomes "1", and false becomes an empty string. These conversions are why echo is unreliable for inspecting values; use var_dump() when debugging.


Boolean Context: Conditions

Any value used as a condition is converted to bool. The falsy values are false, 0, 0.0, "", "0", [], and null; everything else is truthy.

<?php
$values = [0, "0", "", "0.0", " ", [], [0], null, "false"];

foreach ($values as $value) {
    echo str_pad(json_encode($value), 8) . ($value ? "truthy" : "falsy") . "\n";
}

Output

0       falsy
"0"     falsy
""      falsy
"0.0"   truthy
" "     truthy
[]      falsy
[0]     truthy
null    falsy
"false" truthy

json_encode() is used here only to show each value with its quotes. The details of boolean conversion are covered in PHP Booleans.


Comparison Context: Loose Comparison (==)

This is the area where type juggling causes the most bugs.

How == Works

== compares values after converting them to a common type. === compares values and types, with no conversion.

PHP 8 Comparison Rules

OperandsWhat PHP Does
Both numeric strings, or number with numeric stringCompare as numbers
Number with non-numeric stringConvert the number to a string and compare as strings (PHP 8.0+)
Two non-numeric stringsCompare as strings
null with a stringConvert null to "" and compare as strings
bool or null with anything elseConvert both sides to bool
ArraysEqual if they have the same key/value pairs (values compared loosely)

Example

<?php
$tests = [
    ['42', '42.0'],
    [42, ' 42'],
    [42, '42abc'],
    [0, 'abc'],
    [0, ''],
    ['1', '01'],
    ['10', '1e1'],
    [100, '1e2'],
    [null, false],
    [null, 0],
    [null, ''],
    [null, '0'],
    ['0', false],
    [[], false],
    [true, 'false'],
];

foreach ($tests as [$a, $b]) {
    printf("%-8s == %-8s %s\n", var_export($a, true), var_export($b, true), var_export($a == $b, true));
}

Output

'42'     == '42.0'   true
42       == ' 42'    true
42       == '42abc'  false
0        == 'abc'    false
0        == ''       false
'1'      == '01'     true
'10'     == '1e1'    true
100      == '1e2'    true
NULL     == false    true
NULL     == 0        true
NULL     == ''       true
NULL     == '0'      false
'0'      == false    true
array (
) == false    true
true     == 'false'  true

Understanding the Surprising Results

  • '1' == '01', '10' == '1e1' — both strings look numeric, so PHP compares them as numbers. Two different strings are "equal". This matters for codes, tokens, version strings, and hashes that happen to contain only digits.
  • 42 == '42abc' is false in PHP 8 — '42abc' is not numeric, so 42 is converted to "42" and the strings differ. In PHP 7, this was true.
  • 0 == 'abc' is false in PHP 8 — in PHP 7 it was true, which caused real security bugs (for example, a check comparing against 0 passed for any string).
  • null == 0 is true, but null == '0' is false — with an integer, both sides become booleans (false == false); with a string, null becomes "", and "" == "0" is false as strings.
  • true == 'false' — any non-empty string is truthy.
  • The empty array is printed by var_export() across two lines (array ( and )), which is why that row looks different.

The Fix: Use ===

<?php
var_dump('1' === '01');
var_dump(null === 0);
var_dump('0' === false);

Output

bool(false)
bool(false)
bool(false)

Strict comparison never converts types, so every result above is what most people expect. Use === and !== by default, and use == only when you deliberately want type conversion.

Comparing Secrets

For comparing passwords, tokens, or signatures, === is still not enough — it can leak timing information. Use hash_equals() for tokens and password_verify() for passwords (see Password Hashing).


Hidden Loose Comparisons in Functions

Several commonly used constructs compare loosely by default.

<?php
var_dump(in_array("1e1", ["10"]));
var_dump(in_array(null, [0]));
var_dump(in_array("abc", [0]));

var_dump(in_array("1e1", ["10"], true));
var_dump(in_array(null, [0], true));

Output

bool(true)
bool(true)
bool(false)
bool(false)
bool(false)

Without the third argument, in_array() uses ==. Passing true enables strict comparison. The same applies to array_search() and array_keys($array, $value).

Note: in_array("abc", [0]) was true in PHP 7 and is false in PHP 8 because of the comparison change described above.

switch

<?php
$status = "0";

switch ($status) {
    case false:
        echo "Matched false\n";
        break;
    case "0":
        echo "Matched \"0\"\n";
        break;
}

echo match ($status) {
    false => "Matched false",
    "0" => "Matched \"0\"",
};

Output

Matched false
Matched "0"

switch compares with ==, so "0" == false matches the first case. match (PHP 8.0+) compares with ===, so it finds the intended arm. Prefer match when strict comparison matters; see PHP Match.

Sorting Mixed Types

Functions like sort() compare values loosely by default. Sorting arrays that mix numbers and strings can produce unexpected orders. Keep arrays to a single type, or pass sorting flags such as SORT_STRING or SORT_NUMERIC.


Array Key Juggling

Array keys can only be integers or strings. PHP converts other key types automatically:

<?php
$data = [];

$data["8"] = "string eight";
$data[8] = "integer eight";
$data["08"] = "string zero-eight";
$data[2.0] = "float two";
$data["1.5"] = "string one-point-five";

var_dump($data);

Output

array(4) {
  [8]=>
  string(13) "integer eight"
  ["08"]=>
  string(17) "string zero-eight"
  [2]=>
  string(9) "float two"
  ["1.5"]=>
  string(21) "string one-point-five"
}

Key Conversion Rules

Key GivenStored AsReason
"8"8 (int)A string containing a canonical decimal integer becomes an int
88Same key as "8", so the value is overwritten
"08""08" (string)Leading zero — not canonical, stays a string
2.02 (int)Floats are truncated to int
"1.5""1.5" (string)Not an integer string, stays a string
true / false1 / 0Booleans become integers

Why This Matters

  • $data["8"] and $data[8] are the same element. Data read from a form (string IDs) and from a database (integer IDs) still matches.
  • "08" and 8 are different elements, which can create duplicate entries when IDs are formatted inconsistently.
  • Using a float with a fractional part as a key ($data[1.5]) truncates it to 1 and triggers a deprecation notice since PHP 8.1 ("Implicit conversion from float 1.5 to int loses precision").

Function Arguments: Coercive Mode

By default, PHP runs in coercive typing mode. When a function declares scalar parameter types (int, float, string, bool), PHP converts compatible arguments automatically.

Example

<?php
function double(int $number): int
{
    return $number * 2;
}

var_dump(double("21"));
var_dump(double(4.0));
var_dump(double(true));

try {
    double("twenty");
} catch (TypeError $e) {
    echo "TypeError: " . $e->getMessage() . "\n";
}

Output

int(42)
int(8)
int(2)
TypeError: double(): Argument #1 ($number) must be of type int, string given, called in /path/script.php on line 12

How It Works

  • "21" is a numeric string, so it is converted to 21.
  • 4.0 has no fractional part, so it becomes 4. (A float with a fractional part, such as 4.5, would be truncated with a deprecation notice since PHP 8.1.)
  • true becomes 1.
  • "twenty" cannot be converted to an integer, so PHP throws a TypeError — type declarations reject clearly incompatible values even in coercive mode.

Return values are coerced the same way: a function declared : int that returns "5" returns int(5).

Strict Mode

Adding declare(strict_types=1); as the first statement of a file disables this coercion for calls made from that file:

<?php
declare(strict_types=1);

function double(int $number): int
{
    return $number * 2;
}

try {
    double("21");
} catch (TypeError $e) {
    echo "TypeError: " . $e->getMessage() . "\n";
}

Output

TypeError: double(): Argument #1 ($number) must be of type int, string given, called in /path/script.php on line 10

In strict mode, only the exact type is accepted — with one exception: an int is allowed where a float is expected, because that conversion never loses information. Strict mode is covered in Strict Types.


Practical Example: A PIN Check Gone Wrong

The following code compares a submitted PIN with a stored PIN.

<?php
$storedPin = "0042";          // stored as a string
$submittedPin = "42";         // user typed 42

if ($submittedPin == $storedPin) {
    echo "Loose: access granted\n";
}

if ($submittedPin === $storedPin) {
    echo "Strict: access granted\n";
} else {
    echo "Strict: access denied\n";
}

Output

Loose: access granted
Strict: access denied

What Went Wrong

  1. Both values are numeric strings, so == compares them as numbers: 42 == 42.
  2. The user entered a PIN that is not the stored PIN, yet loose comparison accepted it.
  3. === compares the strings exactly and correctly rejects the input.

PINs, phone numbers, ZIP codes, OTPs, and similar codes are identifiers, not quantities. Keep them as strings and compare them strictly (or with hash_equals() for secrets).


Common Mistakes

Using == for Codes and Identifiers

Effect: "0042" == "42" and "1e3" == "1000" are true.

Fix: Use ===.

Relying on PHP 7 Comparison Behavior

Effect: Code that expected 0 == "abc" to be true behaves differently in PHP 8.

Fix: Compare explicitly and strictly; review loose comparisons when upgrading.

in_array() Without Strict Mode

Effect: in_array(null, [0]) is true; in_array("1e1", ["10"]) is true.

Fix: Always pass true as the third argument unless loose matching is intended.

switch With Mixed Types

Effect: "0" matches case false.

Fix: Use match, or normalize the value's type before the switch.

Arithmetic on Unvalidated Input

Effect: TypeError for non-numeric strings, or warnings and partial numbers for strings like "42 apples".

Fix: Validate with filter_var() before calculating.

Inconsistent Array Keys

Effect: "08" and 8 become separate entries.

Fix: Normalize keys (for example, cast IDs to int) before using them.


Best Practices

  • Use === and !== by default.
  • Pass true as the strict flag to in_array(), array_search(), and array_keys().
  • Prefer match over switch when types matter.
  • Validate and explicitly convert input at the edges of your application; let internal code work with known types.
  • Treat codes such as PINs, phone numbers, and ZIP codes as strings.
  • Enable declare(strict_types=1); in new code to make type errors visible early.

Official reference: https://www.php.net/manual/en/language.types.type-juggling.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else User-defined Functions Slicing Strings Function Arguments PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators AJAX PHP AJAX Database AJAX XML AJAX Live Search AJAX Poll AJAX with JSON Security Introduction Password Hashing SQL Injection Prevention XSS Prevention Garbage Collection Project: Simple Blog PHP What is OOP PHP Classes/Objects The $this Keyword Access Array Items Update Array Items Add Array Items Remove Array Items Sorting Arrays Multidimensional Arrays Array Destructuring Array Spread Operator Search in Arrays Merge Arrays PHP Abstract Classes PHP Interfaces Polymorphism PHP Traits PHP Static Methods PHP Static Properties Late Static Binding Magic Methods Object Cloning MySQL Database MySQL Connect MySQL Create DB MySQL Create Table MySQL Insert Data MySQL Get Last ID MySQL Insert Multiple MySQL Prepared MySQL Select Data MySQL Where Debugging PHP Project: Image Upload Gallery $_SESSION $_ENV PHP RegEx RegEx Patterns Heredoc and Nowdoc RegEx Modifiers preg_match preg_replace preg_split PHP RegEx Functions Nullable Types Mixed and Never Types Nullsafe Operator PHP Attributes PHP Fibers Readonly Classes Property Hooks (PHP 8.4) Asymmetric Visibility (PHP 8.4) Reflection API SPL Data Structures Composer PHP Form Handling PHP XML Parsers PDO Transactions PDO Error Handling PHP XML Parsers PHP SimpleXML Parser PHP SimpleXML - Get PHP XML Expat Parser PHP DOM Parser PHP XMLReader PHP XMLWriter AJAX Intro PHP Class Constants Encryption with Sodium Recursive Functions Variable Functions Anonymous Functions Closures Arrow Functions First-class Callable Syntax PHP Arrays Indexed Arrays Associative Arrays Create Arrays Variables Scope Static Variables Variable Variables PHP Echo / Print PHP printf / sprintf PHP Data Types PHP Booleans PHP NULL PHP Strings String Functions array_map / array_filter / array_reduce Array Functions What Are Superglobals? $GLOBALS What Is `$_SERVER` ? $_REQUEST $_POST $_GET $_FILES $_COOKIE PHP Type Juggling
Ask about this post
AI Ask about this post

Ask questions about PHP Type Juggling and get answers drawn from it.

Signed-in readers only.