cswork / libraryOpen practice workspace

Articles

PHP Type Comparison Interview Questions: Loose Equality, Numeric Strings, and Strict Types

Prepare PHP type comparison questions on loose equality, numeric strings, strict identity, input validation, and PHP 8 changes with worked examples.

A PHP comparison interview is rarely about memorizing one truth table. It asks whether you can identify the boundary at which a value changes type, predict the branch that runs, and make the code's intent explicit. Start with both value and type: integer 42 and string "42" can compare equal with == while failing ===. That distinction becomes consequential when a request parameter, database field, or identifier arrives as text.

This article scopes examples to PHP 8.x. The PHP comparison manual, numeric-string manual, and type comparison tables are the official facts. The bug scenarios and interview prompts are original editorial exercises, not candidate reports from a named employer. For a related input boundary, try Determine Whether a String Is a Valid Number and specify the accepted grammar before comparing values.

Interview question: what do == and === actually check?

The PHP manual defines == as equality after type juggling where applicable and === as identity: equal value and equal type. Predict these in PHP 8.x before running them:

var_dump(42 == "42");      // true
var_dump(42 === "42");     // false
var_dump(0 == "foo");      // false in PHP 8.x
var_dump(false == "0");    // true
var_dump(false === "0");   // false

The third case is version-sensitive. PHP 8 changed number-versus-nonnumeric-string loose comparisons: arbitrary text is no longer treated as numeric zero in that situation. The implemented PHP RFC on saner string-to-number comparisons documents the change. This improvement does not turn == into a general validation tool. Booleans have their own conversion rules, and numeric strings can still compare numerically.

Say each step aloud: identify the operand types; ask whether the operator permits conversion; identify the rule; then give the result. Avoid saying “PHP always converts the string to a number.” PHP 8 behavior depends on whether it is numeric. Avoid the opposite mistake, “PHP 8 made loose comparisons safe.” It changed one family of surprises while other cross-type matches remain.

The declare(strict_types=1) directive is a follow-up trap. It affects scalar type declarations at function call and return boundaries under the relevant rules. It does not replace == with === or make membership checks strict by default. If the bug is an identity check, change the operator or normalization contract. If the bug is an incorrectly typed function call, examine the declaration and the calling file. Name the actual boundary rather than prescribing a blanket switch.

Why do numeric strings create subtle matches?

A string with a recognized numeric representation may participate in numeric comparison. The numeric-string specification defines the grammar, including signs, decimals, exponents, and whitespace under PHP 8 rules. The comparison manual says two numeric strings, or a number and a numeric string, compare numerically. Formatting can therefore disappear under loose equality even when the bytes differ.

var_dump("01" == 1);          // true
var_dump("01" === "1");       // false
var_dump("1e2" == 100);       // true
var_dump(is_numeric(" 42 ")); // true in PHP 8.x

These cases do not mean you should cast every string. A customer ID "00123" may be opaque, with meaningful leading zeroes. A price may need a decimal representation with business rules that binary floats do not provide. A pagination count may legitimately become an integer after validation and bounds checking. The correct normalization depends on the domain: parse a count, preserve an account code as text, and model money with required precision. That is editorial design advice based on official conversion rules.

Ask, “When are '01' and 1 supposed to be the same?” For a count, validate and convert before comparing integers. For a record key, compare canonical strings with === and decide whether case, leading zeroes, and whitespace matter. That clarification prevents a truth-table answer from becoming a production bug.

Where can loose comparison hide outside ==?

The in_array documentation states that its third parameter is a strict flag, defaulting to false. Without it, membership can accept a value whose type differs from the intended type. A switch statement also relies on loose comparison behavior, including numeric-string rules documented in the comparison manual. By contrast, the match documentation says arm selection uses strict identity.

$allowed = ["0", "1"];
var_dump(in_array(0, $allowed));       // true
var_dump(in_array(0, $allowed, true)); // false

$result = match (0) {
    "0" => "text zero",
    0 => "integer zero",
};

The result is "integer zero." A migration from switch to match can deliberately tighten matching but can also alter branches for HTTP query parameters, which normally arrive as text. If the team expected "0" and 0 to share a branch, strict match forces a normalization decision instead of inheriting switch behavior.

There is a presence-versus-truthiness trap too. The empty manual treats string "0" as empty. An external ID equal to "0" can be rejected by an empty check. Examine key presence, type, and domain rule separately. A “required nonempty string with '0' allowed” needs an existence test, a string type check, and an explicit length or whitespace policy. An isset check treats null differently, so it also needs to fit the contract. The fix is not simply swapping one helper for another.

Interview exercise: fix a false missing-field error

An API receives JSON with provider_id as a string. The provider permits "0" as an identifier. A handler checks empty and rejects it. First state the contract: the key is required, the value must be a string, the empty string is invalid, and whitespace-only strings need an explicit rule. Write fixtures for absent key, null, integer 0, string "0", empty string, and whitespace-only string before changing implementation.

A schematic fix is:

if (!array_key_exists('provider_id', $payload)) {
    throw new InvalidArgumentException('Missing provider_id');
}
$id = $payload['provider_id'];
if (!is_string($id) || $id === '') {
    throw new InvalidArgumentException('Invalid provider_id');
}

If whitespace-only values are invalid, add a documented trim or whitespace check. Do not silently trim an identifier whose spaces may be meaningful. The snippet does not establish uniqueness or authorization; it handles presence and type only. In an interview, name the next boundary: use the validated ID as a string in lookups, and compare it strictly with other string IDs.

This exercise is stronger than “use triple equals” because it distinguishes missing, null, wrong type, and valid zero-looking text. It is also a useful regression test for a framework request object or ORM layer that may cast data before the handler sees it. Inspect the actual type at that boundary, then test the function with the same representation.

How do you compare values from an API or database?

Start by asking what the source promises. A JSON number and JSON string are different representations. A URL query parameter typically arrives as text; a database driver may return a numeric column as int or string depending on driver and settings. A printed value does not prove its runtime type. Use get_debug_type or var_dump in a local diagnostic, and inspect the serializer or driver contract.

Next, decide whether the operation is numeric or identity-based. For a count, validate syntax, parse, enforce range, then compare integers. For an identifier, preserve text and normalize only under a written rule. For a boolean flag, do not equate arbitrary truthy strings with true; list accepted tokens explicitly. Reject invalid input rather than let it find a loose match.

The is_numeric function answers whether PHP recognizes a numeric string; it does not enforce a business grammar. A quantity might forbid scientific notation, whitespace, leading plus signs, decimals, or out-of-range values. If a prompt says “unsigned decimal integers from 1 to 100,” write that validator instead of using == against 100. Explain the treatment of "001", "1e2", and " 1 " under your rule.

How can a boolean comparison bypass a text rule?

Boolean comparisons deserve their own trace. PHP converts the other operand to a boolean in a loose comparison with a boolean. The string "0" becomes false, while many other nonempty strings become true. Therefore false == "0" is true, but true == "false" is also true: the word "false" is a nonempty string, not a parsed boolean token. The PHP type comparison tables are a useful check when this result feels counterintuitive.

Imagine a permission flag from an HTTP form. If the contract accepts only the literal strings "yes" and "no", comparing the field to true is the wrong operation. Validate that it is a string and one of those two tokens, then map it to a boolean deliberately. The same reasoning applies to a JSON boolean: verify that the decoder preserved a boolean before comparing it with ===. A mixed text-and-boolean field should be treated as a schema problem rather than patched with a broad truthiness check.

In an interview, explain the failure mode with two fixtures: "false" must not authorize access, and the boolean false must not be mistaken for a missing field if it is a valid supplied value. A test that only covers "true" will miss the dangerous branch. Also ask where the original type is first available; once a framework normalizes input, an application-level comparison cannot recover whether it began as text or a JSON boolean without an explicit contract.

How would you test a PHP 7-to-8 comparison change?

Build a truth table with the actual runtimes used in a migration. Include 0 against "foo", 0 against "0", false against "0", 42 against "42", and application-specific strings. Record both == and === where useful. The PHP RFC explains the broad version shift; tests prove what your deployed runtime does with your values.

Then search indirect comparison sites: membership without the strict flag, switch branches, sorting callbacks, authorization guards, and request validation. Do not automatically replace every loose comparison; some intentionally accept numeric representations. The review goal is explicit conversion and fixture coverage.

A valuable negative test asserts that "0" is accepted as a provider ID, integer 0 is rejected if the contract requires string, missing is rejected, and "foo" never becomes numeric zero. For a numeric count, the policy may differ after deliberate parsing. A good answer identifies which inputs must differ, not only which should match.

Practice five linked prompts

In each exercise, name the source type, authorized conversion, and comparison that proves the rule.

QuestionPHP connectionFollow-up to answer aloud
Validate a simplified numeric stringDefine grammar before castingIs "1e2" permitted or only decimal digits?
Implement KV store and plan type conversionsKeep key identity stableCan "01" and 1 identify the same key?
Design input validation and error handlingSeparate absence, null, invalid textWhere does validation end and normalization begin?
Stack Machine Interpreter with Typed Int and String ValuesTrack value and type togetherShould mixed types compare at all?
Determine Whether a String Is a Valid NumberNumeric string is a contractWhich whitespace and exponent forms count?

Try Validate a simplified numeric string first. State a grammar, then explain why validation precedes conversion. For every surprising expression, write both operand types beside the values; that habit works on a whiteboard and during code review.

Sources and Further Reading