skip to content

Operators & Equality

PHP 8 changed how == compares numbers with non-numeric strings, and added ?-> next to ?? and <=>. Interviewers use equality puzzles to test whether your PHP knowledge is post-8.0.

part ofPHPoverview, primer and where to startread it →
on this pageshow

explore

questions

6

In PHP 8, what is the difference between the == and === operators, and why do most codebases default to ===?

level: juniorimportance: must knowfreq 88%

answer

  1. value only vs value plus type
  2. type juggling happens before ==
  3. number vs non-numeric string changed in 8.0
  4. bool or null operand: both become bool
  5. != and <> loose, !== strict

basics

~20 s

== compares values after type juggling, so an int and a string can be equal; === is true only when type and value both match. Loose rules still surprise after PHP 8.0, so === is the safe default.

solid answer

~40 s

`==` is **loose equality**: when the operand types differ, PHP converts one or both before comparing, so `1 == "1"` and `null == false` are true. `===` is **identity**: it is true only when both operands have the same type and the same value, with no conversion. PHP 8.0 removed the worst loose case: a number compared with a *non-numeric* string is now compared as strings, so `0 == "foo"` became false. The other rules stayed: two numeric strings still compare as numbers (`"1" == "01"` is true) and a `bool` or `null` operand still casts both sides to `bool`. Because a reader has to replay those rules to predict `==`, most teams use `===` and `!==` by default and reach for `==` only when conversion is the intent.

code

php · 8 lines
php
<?php
var_dump(0 == 'foo');     // bool(false) since PHP 8.0, true before
var_dump('1' == '01');    // bool(true): numeric strings compare as numbers
var_dump(100 == '1e2');   // bool(true)
var_dump(null == false);  // bool(true): both sides cast to bool
var_dump('abc' == true);  // bool(true)
var_dump('1' === '01');   // bool(false): same type, different value
var_dump(1 === 1.0);      // bool(false): int vs float

go deeper

for a junior

Know that == juggles types and === checks type and value, and give one example that differs, such as 1 == "1" versus 1 === "1".

for a middle

Explain the comparison table: numeric strings compare as numbers, a bool or null operand casts both sides to bool, and PHP 8.0 changed number versus non-numeric string.

for a senior

Show that PHP 8.0 fixed only one case, name the ones that remain (numeric strings, bool casts), and argue for === plus explicit casts in reviewed code.

for a principal

Frame loose comparison as a codebase policy question: enforce strict comparison through review rules and static analysis rather than individual memory of the juggling table.

## Two operators, two questions PHP has two equality operators, and they ask different questions: - **`==` (loose equality)** asks: *after converting the operands to a common type, are the values equal?* The conversion is called **type juggling**. - **`===` (identity, strict equality)** asks: *do both operands have the same type and the same value?* No conversion happens. An `int` is never identical to a `string`, and `1 === 1.0` is false because one is `int` and the other `float`. Their negations follow the same split: `!=` and `<>` are the same loose inequality, `!==` is the strict one. ## What == does before it compares The manual's "Comparison with Various Types" rules decide how `==` converts. The ones that matter in everyday code: | Operands | What PHP does | Example | |---|---|---| | two numeric strings | compares them as numbers | `"1" == "01"` is true | | number and numeric string | compares as numbers | `100 == "1e2"` is true | | number and non-numeric string | since 8.0: casts the number to string, compares strings | `0 == "foo"` is false | | `bool` or `null` and anything | converts both sides to `bool` | `null == false` is true | | `null` and a string | treats `null` as `""` | `null == ""` is true | | two arrays | same key/value pairs, any order, loose values | `[1 => 'b', 0 => 'a'] == [0 => 'a', 1 => 'b']` is true | For arrays, `===` additionally requires the same order and the same types for every element. ## The PHP 8.0 change Before PHP 8.0, comparing a number with *any* string converted the string to a number first, and a string with no leading digits became `0`. That made `0 == "foo"` true, which produced real bugs in lookups and validation. PHP 8.0 changed the rule for **non-numeric** strings only: | Comparison | PHP 7.x | PHP 8.0 and later | |---|---|---| | `0 == "0"` | true | true | | `0 == "foo"` | true | **false** | | `0 == ""` | true | **false** | | `42 == "42foo"` | true | **false** | | `42 == " 42"` | true | true | What counts as a numeric string (leading and trailing whitespace, exponent notation) is its own topic; the point for equality is that numeric strings still compare numerically. ## Cases that still surprise in PHP 8.5 PHP 8.0 did not make `==` safe. These all hold on the current release: 1. `"1e3" == "1000"` is **true**: both are numeric strings, so they compare as numbers. 2. `"abc" == true` is **true**: a `bool` operand casts the string to `bool`, and a non-empty string other than `"0"` is `true`. 3. `null == 0` is **true**, and so is `null == []`: both sides become `false`. 4. `"0" == false` is **true**, while `"0.0" == false` is false, because only `""` and `"0"` are falsy strings. None of these is a bug; each follows the table. The problem is that a reader has to run the table in their head, and the value that arrives at runtime (from a form, a JSON body, a database driver) may not have the type the author imagined. ## Why === is the default Most style guides and reviewers default to `===` for three reasons: - **Predictability.** `===` has one rule. `==` has a table that changed in 8.0 and still depends on the runtime type of both operands. - **Security.** Loose comparison has a history of authentication and token-check bypasses, where an attacker supplies a value whose juggled form matches. - **Static analysis.** Tools that flag `==` in reviews exist precisely because the intent of `==` is ambiguous. `declare(strict_types=1)` does **not** change this. It controls whether scalar arguments and return values are coerced at function-call boundaries; the `==` operator keeps its juggling rules in a strict file. ## When == is still reasonable Loose comparison is not forbidden. It is a deliberate choice when you *want* conversion, for example comparing two values you already know are numeric but that may arrive as `int` or numeric `string`. Even then, many teams prefer an explicit cast followed by `===`, because the cast documents the intent in the code rather than in the reader's memory of the table.

  • Does declare(strict_types=1) make == behave like === in that file?
    No. `strict_types` only changes how scalar arguments and return values are checked when calls are made from that file: a mismatched scalar throws `TypeError` instead of being coerced. The `==` operator still type-juggles exactly as before, so you still need `===` for strict comparison.
  • How do == and === differ when both operands are arrays?
    For arrays, `==` is true when both have the same key/value pairs, in any order, with values compared loosely. `===` also requires the same order and the same types for every value. So `[1 => 'b', 0 => 'a'] == [0 => 'a', 1 => 'b']` is true, but `===` on them is false.
  • Why is null == 0 true in PHP 8 when 0 == "foo" is not?
    They hit different rules. When either operand is `bool` or `null`, PHP converts both to `bool`, and `null` and `0` are both `false`. The PHP 8.0 change only affected a number compared with a non-numeric string, which is now compared as strings.

saying these in an interview costs you the question

  • Claims === converts both operands to a common type before comparing.
  • Says PHP 8 made == strict, so loose-comparison bugs are gone.
  • Believes declare(strict_types=1) turns every == into ===.
  • Thinks 0 == "abc" is still true on PHP 8.
  • Thinks null == false is false because the types differ.
open as a page

In PHP, how do the ??, ?:, ??= and ?-> operators differ, and when does each one fall back?

level: middleimportance: must knowfreq 70%

basics

~20 s

?? falls back when the left side is null or undefined, without a warning; ?: falls back on any falsy value; ??= assigns only when null or unset; ?-> returns null for a null object and skips the rest of the chain.

open as a page

In PHP, what does the <=> spaceship operator return, and how do you use it to sort by several fields?

level: middleimportance: should knowfreq 45%

basics

~20 s

$a <=> $b returns an int below, equal to or above zero when $a is less than, equal to or greater than $b. Comparing arrays of fields, [$a->x, $a->y] <=> [$b->x, $b->y], gives a multi-key comparator for usort().

open as a page

A PHP checkout checks coupons with $input == $coupon->code and accepts codes that should fail; which loose-comparison rules cause it, and did PHP 8 fix them?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Loose == accepts numeric look-alikes ("1e2" == "100"), a JSON true against any ordinary code, and null against an empty code. PHP 8.0 fixed only int 0 matching a non-numeric code; require a string and compare with ===.

open as a page

In PHP, why does $ok = validate($code) and redeem($code); leave $ok holding only the validation result, and which other precedence traps catch developers?

level: seniorimportance: should knowfreq 38%

basics

~20 s

and, or and xor bind more loosely than =, so the line runs as ($ok = validate($code)) and redeem($code), discarding redeem's result. && and || bind tighter than =. Nested unparenthesised ternaries and == inside bitwise & are the other classic traps.

open as a page

In PHP 8.5, what do 7 / 2, 6 / 2, 1 / 0 and -2 ** 2 evaluate to, and why?

level: middleimportance: nice to knowfreq 25%

basics

~20 s

7 / 2 is float(3.5) and 6 / 2 is int(3), because / returns an int only when two ints divide exactly; 1 / 0 throws DivisionByZeroError since PHP 8.0; -2 ** 2 is int(-4) because ** binds tighter than unary minus.

open as a page