PHP Language Basics
Core PHP semantics — loose versus strict comparison and type juggling, include and require, null handling with isset/empty and the null-coalescing operators, string literals and heredoc, variable scope and references, and the match expression.
16 questions
JuniorTheoryVery commonHow do isset, empty and is_null differ when checking a variable?
How do isset, empty and is_null differ when checking a variable?
isset is true when the variable exists and is not null; being a language construct, it stays quiet on an undefined variable. empty is true for any falsy value — '', 0, '0', [], null. is_null is a function that tests only for null.
Common mistakes
- ✗Assuming
isset($x)is true when$xholdsnull - ✗Using
emptyon a numeric input, where0and'0'are treated as absent - ✗Calling
is_nullon a possibly undefined variable and getting a warning
Follow-up questions
- →Why can
issettake several arguments at once, and what does it return then? - →Which of the three would you use to tell an absent form field from an empty one?
JuniorTheoryVery commonWhat is the difference between == and === when comparing values in PHP?
What is the difference between == and === when comparing values in PHP?
== is loose: it may convert the operands to a common type, so 1 == '1' and null == false are true. === is strict: it holds only when the type and the value match, so 1 === '1' is false. For objects === means the same instance.
Common mistakes
- ✗Thinking
===compares object contents, when it requires the very same instance - ✗Believing
null == falseis false because the two types differ - ✗Using
==on user input, where two numeric strings like'1e3' == '1000'compare numerically
Follow-up questions
- →How does
==compare two objects of the same class, and what does===require instead? - →Which comparison does
in_arrayuse by default, and how do you make it strict?
JuniorTheoryCommonHow does passing a variable by reference with & differ from by value?
How does passing a variable by reference with & differ from by value?
By default PHP passes by value: the callee gets its own copy, so reassigning it leaves the caller's variable untouched. A &$x parameter binds both names to one value, so writes inside are visible outside. Objects pass a handle by value.
Common mistakes
- ✗Saying objects are passed by reference, when the handle itself is copied by value
- ✗Expecting a plain array parameter to be modified in place by the callee
- ✗Forgetting
&in aforeach ($items as &$item)loop and wondering why nothing changed
Follow-up questions
- →Why must you
unsetthe loop variable after aforeachthat iterates by reference? - →What happens to the caller's object when the callee reassigns an object parameter?
JuniorTheoryCommonHow does the match expression differ from switch in PHP 8?
How does the match expression differ from switch in PHP 8?
match is an expression: it returns a value, compares arms strictly with ===, never falls through, and throws UnhandledMatchError when nothing matches. switch is a statement: it returns nothing, compares with ==, and falls through.
Common mistakes
- ✗Expecting
matchto compare loosely, somatch('1')hits an1arm - ✗Omitting
defaultand being surprised by anUnhandledMatchErrorat runtime - ✗Writing
breakinside amatcharm as if it could fall through
Follow-up questions
- →How do you express a range check such as an HTTP status class with
match? - →When is
switchstill the better fit despite its loose comparison?
MiddleTheoryCommonWhen does isset disagree with array_key_exists on the same array key?
When does isset disagree with array_key_exists on the same array key?
They disagree exactly when the key exists but holds null: isset($a['k']) is false, array_key_exists('k', $a) is true. isset cannot tell an absent key from a stored null, and ?? follows it. The function needs the array to exist.
Common mistakes
- ✗Using
issetto detect an optional field that may legitimately benull - ✗Assuming
??distinguishes a missing key from a storednullvalue - ✗Calling
array_key_existson an undefined array and getting a TypeError
Follow-up questions
- →Which of the two would you use to tell an absent JSON field from an explicit
null? - →How does
issetbehave on a nested path such as$a['b']['c']when$a['b']is missing?
MiddleTheoryCommonWhat does global $config do inside a function, and why prefer a parameter?
What does global $config do inside a function, and why prefer a parameter?
global $config; imports one name from the global scope as a reference, so assigning to it also rewrites $GLOBALS['config']. A parameter instead states the dependency in the signature and lets a test pass a stub. global hides that coupling.
Common mistakes
- ✗Thinking
globalcopies the value, when it binds a reference to$GLOBALS - ✗Believing
unset($config)afterglobalremoves the global entry itself - ✗Keeping
globalin new code because the value is 'used everywhere anyway'
Follow-up questions
- →What happens to
$GLOBALS['config']when the function callsunset($config)afterglobal? - →How does injecting the value through the constructor change how you test the code?
MiddleTheoryCommonWhat does the third argument of in_array change, and why does it matter?
What does the third argument of in_array change, and why does it matter?
in_array compares with == by default, so in_array(0, ['0']) is true — the numeric string compares as a number. Passing true as the third argument switches to ===, matching type and value. array_search and array_keys take the same flag.
Common mistakes
- ✗Checking a user-supplied token against a whitelist with the default loose mode
- ✗Assuming
in_arrayis strict by default becausematchis - ✗Forgetting that
array_searchneeds the same flag to return a trustworthy key
Follow-up questions
- →How would you decide between a strict
in_arrayand anissetlookup on a flipped array? - →What does
array_searchreturn when it does not find the needle, and why is that a trap?
MiddleTheoryCommonHow does ?? differ from ?:, and what does the ??= assignment do?
How does ?? differ from ?:, and what does the ??= assignment do?
$a ?? $b yields $a when it is set and not null, otherwise $b, and it silences the undefined-index warning. ?: uses truthiness and does warn: 0 ?? 5 is 0, 0 ?: 5 is 5. $a ??= $b assigns only when $a is missing or null.
Common mistakes
- ✗Expecting
??to fall back on0or'', which are set and notnull - ✗Using
?:on$_GET['page']and getting an undefined-key warning - ✗Reading
??=as an unconditional assignment rather than a null-only one
Follow-up questions
- →How does
??behave on a deeply nested key such as$a['b']['c']? - →What does
??do when the array value is present but holdsnull?
MiddleTheoryCommonWhat is type juggling in PHP, and where does the engine convert a value?
What is type juggling in PHP, and where does the engine convert a value?
Type juggling is PHP's implicit conversion of an operand to the type an operation expects: '5' + 3 is int 8, 5 . '3' is '53', if ($x) casts to bool, and '', '0', 0 are falsy. strict_types constrains arguments and returns, not operators.
Common mistakes
- ✗Expecting
declare(strict_types=1)to stop'5' + 3from converting the string - ✗Treating
'0'as truthy because it is a non-empty string - ✗Assuming a numeric string keeps its string type after arithmetic
Follow-up questions
- →Which string values are falsy in a boolean context, and why is
'0'among them? - →What exactly does
declare(strict_types=1)change, and where does it not apply?
JuniorTheoryOccasionalWhy can a PHP function not see a variable defined in the calling scope?
Why can a PHP function not see a variable defined in the calling scope?
Every function body opens its own variable scope, and there is no implicit capture of the enclosing scope as in JavaScript. Outer values must be passed in explicitly — as parameters, in a closure's use list, or via global.
Common mistakes
- ✗Expecting JavaScript-style implicit capture of the enclosing scope inside a closure
- ✗Thinking
globalimports every outer variable rather than one named binding - ✗Assuming a variable defined earlier in the file is visible inside a function body
Follow-up questions
- →How does a closure's
uselist capture a value, and when do you add&to it? - →Which variables are visible inside a function without being passed in at all?
JuniorTheoryOccasionalHow do include, require and their _once variants differ in PHP?
How do include, require and their _once variants differ in PHP?
Both pull another file in and execute it there. On a missing file include emits a warning and the script continues; require raises a fatal error and stops. The _once variants skip a file whose resolved path was already pulled in.
Common mistakes
- ✗Believing
includealso aborts the script when the file is missing - ✗Thinking
_oncecaches the file's output rather than tracking the resolved path - ✗Relying on
require_oncein application code instead of a Composer autoloader
Follow-up questions
- →What does an
includereturn, and how can the included file control that value? - →Why do modern projects almost never write
requireoutside the Composer entry point?
JuniorTheoryOccasionalWhat does the nullsafe operator ?-> do when the left operand is null?
What does the nullsafe operator ?-> do when the left operand is null?
$a?->b() evaluates to null instead of throwing when $a is null, and the rest of the chain is short-circuited — the skipped call's arguments are not evaluated. It guards only against null: a missing method still raises an Error.
Common mistakes
- ✗Expecting
?->to swallow exceptions or missing-method errors, not just anullreceiver - ✗Thinking the arguments of the skipped call are still evaluated
- ✗Chaining
?->everywhere and hiding anullthat should have been a real failure
Follow-up questions
- →How does
?->interact with a long chain such as$a?->b()->c()? - →Why is
?->forbidden on the left-hand side of an assignment?
JuniorTheoryOccasionalHow does a single-quoted PHP string differ from a double-quoted one?
How does a single-quoted PHP string differ from a double-quoted one?
A double-quoted string is parsed: $name and {$obj->prop} interpolate, and escapes like \n become real characters. A single-quoted string is literal — only \' and \\ are escapes. The performance gap between the two forms is negligible.
Common mistakes
- ✗Expecting
'Hello $name'in single quotes to interpolate the variable - ✗Believing single quotes give a meaningful speed win in real applications
- ✗Forgetting
{}around a complex expression such as{$obj->prop}inside double quotes
Follow-up questions
- →When do you need the curly-brace form
{$obj->prop}inside a double-quoted string? - →Which escape sequences are still meaningful inside a single-quoted string?
MiddleTheoryOccasionalWhat does $$name do in PHP, and why do teams discourage it?
What does $$name do in PHP, and why do teams discourage it?
$$name uses the value of $name as another variable's name, resolved at runtime; ${'row_' . $i} builds it from a string expression. It reaches only the current scope, not the caller's. Such a name defeats static analysis, IDE renaming and grep.
Common mistakes
- ✗Thinking
$$namecan read a variable from the calling function's scope - ✗Writing
$$arr['k']without braces, where the parse is ambiguous - ✗Reaching for variable variables where a plain associative array is the right tool
Follow-up questions
- →Why does
$$arr['k']need braces, and which two readings does the parser have to pick from? - →How would you rewrite a block built on
$$nameusing an array instead?
JuniorTheoryRareWhat is the difference between a heredoc and a nowdoc string in PHP?
What is the difference between a heredoc and a nowdoc string in PHP?
Both are multi-line literals opened with <<<. A heredoc uses a bare identifier and acts like a double-quoted string: variables interpolate, escapes are processed. A nowdoc quotes the identifier — <<<'SQL' — and acts like a single-quoted one.
Common mistakes
- ✗Expecting a nowdoc to interpolate
$variablesthe way a heredoc does - ✗Indenting the closing identifier differently from the body, which shifts the string's indentation
- ✗Putting a heredoc with user data straight into SQL instead of binding parameters
Follow-up questions
- →How does the indentation of the closing identifier affect the resulting string?
- →When would you deliberately reach for a nowdoc rather than a single-quoted string?
MiddleTheoryRareHow did PHP 8.0 change a loose comparison between a number and a string?
How did PHP 8.0 change a loose comparison between a number and a string?
PHP 8.0 first asks whether the string is numeric. If it is, both sides compare as numbers, so 100 == '1e2' stays true. If not, the number is cast to a string and they compare as strings — 0 == 'abc' is now false, where PHP 7 said true.
Common mistakes
- ✗Assuming
0 == 'abc'is still true, as it was before PHP 8 - ✗Thinking the change made
==strict —'1' == '01'is still true - ✗Believing the rule touches
===, which never converted anything
Follow-up questions
- →Which strings count as numeric in PHP 8, and does trailing whitespace break that?
- →Why did the pre-8.0 rule make a naive token check with
==a security hole?