GAP·MAP
← all posts
July 11, 2026questions

"PHP interview questions: runtime, types, modern PHP"

PHP loops circle the same ground: how a request lives and dies, why FPM falls over under load, why a deploy still serves old code, and how loose comparison lies. Interviewers grade the mechanism behind your answer, not the keyword. This list pulls from the rubric behind our PHP runtime and PHP types modules and from real loop reports.

At a glance, what tends to get asked and where it maps:

BandTypical questionStudy
junior"What happens when a PHP request comes in?"PHP runtime
middle== vs === and the PHP 8 comparison changePHP types
middle"I deployed but the site serves old code"PHP runtime
senior0e magic-hash auth bypassWeb security
seniorall FPM workers busy, nginx returns 502HTTP protocol

The runtime floor

Every PHP question sits on top of one fact: the process forgets everything at the end of the request.

What happens when a request comes in

The web server hands the request to PHP, PHP runs the script top to bottom, produces output, then the interpreter state is thrown away. Superglobals like $_GET, $_POST, and $_SESSION are rebuilt fresh every time. A plain variable does not survive to the next page load because process memory does not persist. This is shared-nothing: persistence lives in a session store, a database, or a cache.

Trap a static property or global assumed to carry state between requests. It holds within one request only. Reaching for a Redis or Memcached layer is the expected answer, because in-process memory dies with the request. The same fact drives per-request database connections: a fresh one each request unless you pool externally (SQL fundamentals).

Study PHP runtime model

Why all your FPM workers can be busy at once

One FPM worker serves one request at a time. Concurrency equals the number of workers, capped by pm.max_children. The classic outage: a slow database or a slow upstream API holds every worker, the queue fills, and nginx starts returning 502 or 504.

Follow-up "How do you size pm.max_children?" Roughly the RAM available for PHP divided by average process RSS, with headroom. pm.max_requests recycles workers to bound leaks. Blaming "traffic" without naming the slow dependency or the worker cap misses the mechanism, and the 504 itself is an HTTP protocol detail: the gateway giving up on a stalled worker.

Study PHP-FPM worker pools

"I deployed but I still see old code"

PHP compiles to opcodes and OPcache stores them in shared memory across the whole pool. In production you set opcache.validate_timestamps=0 so PHP does not stat every file on every request. The cost: changed files are ignored until an explicit opcache_reset() or an FPM reload. Skip that step in your deploy and the old opcodes keep serving.

// WRONG: deploy script swaps the symlink and walks away
ln -sfn /releases/new /var/www/current
// OPcache still holds the previous release's opcodes

// RIGHT: reload FPM (or opcache_reset) after the swap
ln -sfn /releases/new /var/www/current
systemctl reload php-fpm   // clears the cache, picks up new files

Preloaded classes (opcache.preload) are stronger still: linked into memory at FPM start, they cannot update without a full restart. opcache.jit=tracing buys almost nothing for I/O-bound web requests (a few percent, sometimes negative) because the bottleneck is the database or the network. Real gains show up only in CPU-bound loops like image processing. Selling JIT as a web-request speedup is a red flag.

Study OPcache and preloading


Where type questions separate people

== vs ===, and what PHP 8 changed

=== compares type and value with no coercion. == coerces first, and that is where the bugs live. The PHP 8 rewrite of string-to-number comparison flipped several long-standing results:

'0'    == false      // true  (both falsy, unchanged)
0      == ''         // false in PHP 8, was TRUE in PHP 7
'abc'  == 0          // false in PHP 8, was TRUE in PHP 7
'1e3'  == '1000'     // true  (two numeric strings compare as numbers)

The rule to state precisely: when one operand is a number and the other a string, PHP 8 compares as numbers only if the string is numeric, otherwise it casts the number to string. Before 8 the string was cast to a number, so 0 == "foo" was true.

Study Type juggling and comparison

The 0e magic-hash trap

Trap the PHP 8 change did NOT close the magic-hash hole. Two strings like "0e123" and "0e456" are both numeric strings, so they still compare equal under ==.

// WRONG: loose compare on a hash lets a 0e-collision auth bypass through
if (md5($input) == $stored) { grantAccess(); }
// "0e830400451993494058024219903391" == "0e..." is TRUE

// RIGHT: strict compare, or a constant-time check
if (hash_equals($stored, md5($input))) { grantAccess(); }

Auth code that compares hashes with == is exploitable when both sides land on a 0e-prefixed hash. The fix is === or hash_equals, not the version bump. It lives at the seam of type semantics and web security.

Study Web security

PHP 8 features they expect you to write

Constructor promotion, readonly, backed enums, and match each remove a specific pain. Two facts interviewers dig at: match uses === with no fallthrough and throws \UnhandledMatchError when no arm matches, where switch silently does nothing. readonly is shallow, blocking reassignment of the property but not mutation of an object it holds. Enum cases are singletons, so Suit::Hearts === Suit::Hearts, and backed enums give you ::from() (throws on miss) versus ::tryFrom() (returns null). Mapping request payloads onto typed enums is also an API design concern.

Study Enums, readonly, and match

Arrays copy, objects do not

Arrays assign by value through copy-on-write. Objects assign by handle, so two variables point at one object.

$a = [1, 2, 3];
$b = $a;  $b[] = 4;    // $a is still [1,2,3], the write triggered a copy

$o = new Cart();
$p = $o;  $p->add(1);  // $o AND $p see the new item, same object

Copy-on-write means passing a large array to a read-only function costs nothing until the first write. Follow-up the foreach ($a as &$v) footgun. After a reference loop, $v stays bound to the last element, so the next foreach ($a as $v) overwrites it. Fix with unset($v).

Study Arrays vs objects


What the grader listens for

Red flags treating a static property as cross-request storage, "fixing" a busy FPM pool by raising max_children past available RAM, claiming PHP 8 killed the magic-hash bypass, and comparing password hashes with ==. Any one caps the score no matter how fluent the rest sounds.

The free notes run from the junior floor to the senior internals: PHP runtime and PHP types and OOP. The diagnostic tells you which band you clear.

was this useful?

beta

The interviewer part is in the works.

The diagnostic, personal maps, and AI mock interviews are being finished right now. The notes stay free either way. Leave an email and you'll get the first-cohort invite, plus a month of Pro when it opens.