Type Juggling & Casting

Reviewed & published by Brayan K

PHP is loosely typed, which means it will quietly convert types behind your back. This lesson shows exactly when juggling happens, how to take control with explicit casts, how PHP 8 made comparisons safer, and how strict_types protects your functions.

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️⃣ Implicit Juggling

Juggling happens automatically whenever an operator meets the "wrong" type. Arithmetic operators juggle towards numbers; the . operator juggles towards strings; boolean contexts juggle towards true/false. PHP 8 tightened the rules: adding a string that doesn't start as a number now throws a TypeError rather than guessing.

<?php
// "Type juggling" = PHP silently converting a value's type for an operation.
// It happens because PHP is loosely typed.

echo "5" + 3 . "\n";       // 8     — the string "5" is juggled to int 5

// A LEADING-numeric string still works, with a warning on stderr:
//   "Warning: A non-numeric value encountered"
echo "5 apples" + 3;        // 8     — PHP reads the 5 and warns about the rest
echo "\n";

// A string with no leading number is a TypeError in PHP 8:
try {
    echo "apples" + 3;
} catch (TypeError $e) {
    echo "TypeError: " . $e->getMessage() . "\n";   // Unsupported operand types: string + int
}

echo 5 . 3 . "\n";          // 53    — the . operator juggles both to strings
echo true + true . "\n";   // 2     — booleans juggle to 1 + 1
echo "10" * "2" . "\n";    // 20    — numeric strings juggle to ints
?>

2️⃣ Explicit Casting

When you want a conversion you control, put the target type in parentheses in front of the value: (int), (float), (string), (bool), (array). Note that (int) truncates (it never rounds) and stops at the first non-digit.

<?php
// EXPLICIT casting: you decide the conversion with (type) in front.
$s = "42.9";

var_dump((int) $s);     // int(42)    — truncates, does NOT round
var_dump((float) $s);   // float(42.9)
var_dump((bool) $s);    // bool(true) — non-empty, non-"0" string
var_dump((string) 99);  // string(2) "99"
var_dump((array) "hi"); // array(1) { [0]=> string(2) "hi" }

// Casting a string that starts with digits keeps the leading number.
var_dump((int) "123abc"); // int(123)
var_dump((int) "abc123"); // int(0)   — no leading digits
?>

3️⃣ Loose vs Strict Comparison

The loose == operator juggles both sides to a common type before comparing — the source of PHP's most infamous surprises. PHP 8 fixed the worst of them (0 == "abc" is now false), but numeric strings still compare numerically, so "10" == "1e1" is true. The strict === sidesteps all of it.

<?php
// == juggles types before comparing. === compares value AND type.
var_dump(0 == "0");        // true   — both seen as 0
var_dump(0 == "");         // false  — PHP 8 fixed this old trap
var_dump(0 == "abc");      // false  — PHP 8: non-numeric string is NOT 0
var_dump("1" == "01");     // true   — both numeric strings -> 1
var_dump("10" == "1e1");   // true   — 1e1 is 10 in scientific notation
var_dump(100 == "1e2");    // true   — 1e2 is 100

// Strict === sidesteps ALL of it.
var_dump(0 === "0");       // false  — int vs string
var_dump("1" === "01");    // false  — different strings
?>

4️⃣ Turning Off Juggling: strict_types

Add declare(strict_types=1); as the first line of a file and PHP stops auto-juggling the arguments and return values of typed functions. Pass the wrong type and you get an immediate TypeError — a bug caught now rather than three layers deep later. (An int is still accepted where a float is wanted, because that widening loses nothing.)

<?php
declare(strict_types=1);
// With strict_types on, PHP refuses to silently juggle arguments and
// return values for typed functions — bugs surface immediately.

function addTax(float $price): float {
    return $price * 1.2;
}

echo addTax(10.0) . "\n";   // 12   — a real float, fine
echo addTax(10) . "\n";     // 12   — int 10 is widened to 10.0 (allowed)
// echo addTax("10");        // TypeError! a string is NOT auto-juggled here
?>

5️⃣ Converting User Input Safely

Everything from a form or URL arrives as a string, so converting it is a daily task — and the place where a careless (int) bites. The robust pattern is validate, then cast: filter_var() returns the clean value or false, making "invalid" unmistakable.

<?php
// For USER INPUT (always strings), convert deliberately and validate.
$ageInput = "  29  ";

// 1) trim, 2) check it's numeric, 3) cast.
$ageInput = trim($ageInput);
if (ctype_digit($ageInput)) {
    $age = (int) $ageInput;
    echo "Age is $age\n";        // Age is 29
}

// filter_var is the robust, intention-revealing way to validate+convert.
$n = filter_var("42", FILTER_VALIDATE_INT);
var_dump($n);                     // int(42)
$bad = filter_var("nope", FILTER_VALIDATE_INT);
var_dump($bad);                   // bool(false)  — clearly "invalid"
?>

Now you try. Fill the ___ blanks with the right cast keyword.

<?php
// 🎯 YOUR TURN — fill each ___ to convert a form value safely.
$raw = "73.8";

$asInt   = (___) $raw;   // 👉 cast to int  -> 73 (truncated)
$asFloat = (___) $raw;   // 👉 cast to float -> 73.8
$asBool  = (___) $raw;   // 👉 cast to bool -> true

var_dump($asInt);        // int(73)
var_dump($asFloat);      // float(73.8)
var_dump($asBool);       // bool(true)

// ✅ Expected output:
//    int(73)
//    float(73.8)
//    bool(true)
?>

🧩 Reorder Challenge

These scrambled lines should validate a string and print a result. Put them in the order that prints Valid: 50.

You must define $raw, then trim it, then validate it into $clean before you can test and print it. The === false check is essential because filter_var returns false for invalid input but could legitimately return 0 for the string "0". Output: Valid: 50.

🧠 Quick Recall

Predict the output before revealing each answer.

The first prints 8 (arithmetic juggles "5" to the int 5); the second prints 53 (the . operator juggles both to strings and concatenates).

Prints int(42). Casting to int truncates the fractional part — it does not round to 43.

Prints bool(true). Both are numeric strings, so PHP compares them numerically: 10 equals 10 (1e1 is scientific notation for 10). With === it would be false.

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Juggling & Casting

ExpressionResultWhy
"5" + 38Juggled to numbers
5 . 3"53"Juggled to strings
(int) "42.9"42Truncates, no rounding
(bool) "0"false"0" is the only falsy string
0 == "abc"falsePHP 8 fixed this trap
"10" == "1e1"trueNumeric strings compared as numbers
strict_types=1TypeErrorNo auto-juggling of args

Mini-Challenge: Validate Form Fields

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: validate and total two form fields.
// Form data always arrives as strings. No code filled in — follow the steps.
//
// 1. $qtyRaw = "3", $priceRaw = "19.99" (both strings).
// 2. Validate $qtyRaw with filter_var(..., FILTER_VALIDATE_INT).
// 3. Validate $priceRaw with filter_var(..., FILTER_VALIDATE_FLOAT).
// 4. If BOTH are not false, compute $total = $qty * $price.
// 5. echo "Total: $" . number_format($total, 2);
//    otherwise echo "Invalid input".
//
// ✅ Expected output:
//    Total: $59.97

// your code here
?>

🎉 Lesson Complete!

Practice quiz

What does echo "5" + 3; print?

  • 53
  • "5"3
  • 8
  • A TypeError

Answer: 8. The + operator juggles the numeric string "5" to the int 5, giving 8.

What does echo 5 . 3; print?

  • 53
  • 8
  • 15
  • 5.3

Answer: 53. The . concatenation operator juggles both numbers to strings and joins them: "53".

What does var_dump((int) "42.9"); output?

  • int(43)
  • float(42.9)
  • int(0)
  • int(42)

Answer: int(42). Casting to int truncates the fractional part — it does not round — so it is 42.

What does var_dump("10" == "1e1"); output?

  • bool(false)
  • bool(true)
  • int(10)
  • An error

Answer: bool(true). Both are numeric strings, so PHP compares them numerically; 1e1 is 10, so they are equal.

In PHP 8, what does var_dump(0 == "abc"); output?

  • bool(false)
  • bool(true)
  • int(0)
  • An error

Answer: bool(false). PHP 8 fixed this trap: a non-numeric string is no longer treated as 0, so the result is false.

What does var_dump(0 === "0"); output?

  • bool(true)
  • int(0)
  • bool(false)
  • string(1)

Answer: bool(false). === compares value AND type; an int and a string differ in type, so the result is false.

What does var_dump((int) "abc123"); output?

  • int(123)
  • int(0)
  • int(456)
  • string(6)

Answer: int(0). (int) reads leading digits only; "abc123" has no leading digits, so it casts to 0.

What does var_dump((bool) "0"); output?

  • bool(true)
  • int(0)
  • string(1)
  • bool(false)

Answer: bool(false). "0" is the one string that casts to false; any other non-empty string is true.

What is the difference between juggling and casting?

  • They are the same thing
  • Juggling is implicit; casting is explicit with (int), (float), etc.
  • Casting is automatic; juggling is manual
  • Juggling only works on integers

Answer: Juggling is implicit; casting is explicit with (int), (float), etc.. PHP juggles types automatically during operations; casting is a deliberate (type) conversion you control.

What does filter_var("nope", FILTER_VALIDATE_INT) return?

  • 0
  • null
  • false
  • "nope"

Answer: false. filter_var returns false for invalid input, which is far clearer than a blind (int) cast turning it into 0.

Continue this course

Frequently asked questions

What is type juggling in PHP?

Type juggling is PHP automatically converting a value from one type to another to complete an operation. For example "5" + 3 converts the numeric string to the integer 5 and gives 8, and the concatenation operator turns 5 . 3 into the string "53". It's convenient but implicit, which is how subtle bugs sneak in. In PHP 8, adding a non-numeric string like "5 apples" + 3 now throws a TypeError instead of silently juggling, which is a welcome safety improvement.

What is the difference between juggling and casting?

Juggling is implicit — PHP does it for you, automatically, during an operation. Casting is explicit — you write (int), (float), (string), (bool), or (array) in front of a value to force a specific conversion that you control. Prefer casting when intent matters: (int) "42.9" deliberately truncates to 42, whereas relying on juggling leaves the conversion (and its surprises) up to PHP.

What does declare(strict_types=1) do?

Placed as the very first statement in a file, declare(strict_types=1) turns off implicit argument and return-value juggling for functions that have type declarations. Passing a string to a parameter typed as int then throws a TypeError instead of being silently converted. One sanctioned exception: an int is still allowed where a float is expected, because that widening is lossless. Strict types catch whole classes of bugs early and are strongly recommended for new code.

How should I convert user input to numbers safely?

Form and query data always arrives as strings, so convert deliberately. Trim the value, then validate before casting. filter_var($v, FILTER_VALIDATE_INT) returns the integer on success or false on failure, which is far clearer than a blind (int) cast that silently turns "abc" into 0. For digit-only checks use ctype_digit(), and for floats use FILTER_VALIDATE_FLOAT. Validating up front means bad input is rejected loudly instead of corrupting your data quietly.

Related lessons