HireHireInterview Quizzes › PHP Developer

PHP Developer Interview Questions

Think you're ready? These are the questions that actually decide PHP Developer interviews. Warm up on Easy — then face the Hard round, where 95% of candidates crumble. 30 questions across 3 levels, instant score, completely free.

30Questions
3Difficulty levels
95%Fail the hard round
FreeInstant score
Easy
Warm-up · 10 Qs
Medium
Practical · 10 Qs
Hard
Brutal · 10 Qs
⚡ Take the PHP Developer quiz — get your score →

The PHP Developer interview questions

Below are the real questions, grouped by difficulty. Expand any one to reveal the correct answer and why — or take the timed quiz for a score you can share. Can you clear the Hard round?

Easy round 10 questions

In PHP, what is the difference between the '==' and '===' operators?
  • A. '==' compares value only; '===' compares both value and type ✓
  • B. '==' compares type only; '===' compares value only
  • C. Both are identical and interchangeable
  • D. '===' is used only for object comparison
Correct answer: A. '==' performs loose comparison with type juggling, while '===' checks that both value and data type match.
Which PHP superglobal contains data sent to a script via an HTTP POST request?
  • A. $_GET
  • B. $_POST ✓
  • C. $_REQUEST_POST
  • D. $_HTTP_POST
Correct answer: B. $_POST is the built-in superglobal that holds form data submitted with the HTTP POST method.
To prevent SQL injection when using PDO, the recommended approach is to:
  • A. Escape input with addslashes() before concatenating
  • B. Use prepared statements with bound parameters ✓
  • C. Wrap the query in htmlspecialchars()
  • D. Validate that the input length is under 255 characters
Correct answer: B. Prepared statements with bound parameters separate SQL code from data, so user input cannot alter the query structure.
What does the 'session_start()' function do in PHP?
  • A. Creates a new database connection for the user
  • B. Starts a new session or resumes an existing one, enabling $_SESSION ✓
  • C. Permanently stores a cookie on the client for 30 days
  • D. Logs the user into the application automatically
Correct answer: B. session_start() initializes session data, either starting a fresh session or resuming the current one so $_SESSION can be used.
In a Laravel application, which Artisan command creates a new database migration file?
  • A. php artisan make:migration create_users_table ✓
  • B. php artisan db:migrate create_users_table
  • C. php artisan generate:migration users
  • D. php artisan new:migration create_users_table
Correct answer: A. 'php artisan make:migration' is the correct command to scaffold a new migration file in Laravel.
What is the correct way to include a file so that a fatal error is thrown if the file is missing?
  • A. include
  • B. include_once
  • C. require ✓
  • D. include_optional
Correct answer: C. require causes a fatal E_COMPILE_ERROR and halts the script if the file cannot be found, unlike include which only emits a warning.
Which HTTP status code should a PHP REST API return when a requested resource does not exist?
  • A. 200 OK
  • B. 301 Moved Permanently
  • C. 404 Not Found ✓
  • D. 500 Internal Server Error
Correct answer: C. 404 Not Found is the standard status code indicating the requested resource could not be found on the server.
What does the 'public function __construct()' method represent in a PHP class?
  • A. A method that runs automatically when an object is destroyed
  • B. A method that runs automatically when a new object is instantiated ✓
  • C. A static method callable without an instance
  • D. A method that must be called manually after object creation
Correct answer: B. __construct() is the constructor, invoked automatically when a new instance of the class is created.
In Composer, what is the primary purpose of the composer.lock file?
  • A. It lists which developers can modify dependencies
  • B. It records the exact versions of installed dependencies for reproducible installs ✓
  • C. It encrypts the vendor directory for security
  • D. It stores database credentials for the project
Correct answer: B. composer.lock pins the exact resolved versions of every dependency so all environments install identical package versions.
What will 'echo gettype(5 + "3 apples");' output conceptually about the result of 5 + "3 apples" in PHP 8?
  • A. It concatenates to the string "53 apples"
  • B. It produces integer 8 after a TypeError is thrown
  • C. It throws a TypeError because the string is non-numeric
  • D. It evaluates to integer 8 with a warning about the non-numeric string ✓
Correct answer: D. In PHP 8, a leading-numeric string like "3 apples" yields 3 in arithmetic but emits an E_WARNING about the trailing non-numeric part, giving 8.

Medium round 10 questions

Given `$a = '0'; $b = false;`, what does `var_dump($a == $b)` output, and why?
  • A. bool(true), because the string '0' is loosely equal to false ✓
  • B. bool(false), because a string and a boolean are never comparable
  • C. bool(true), because any non-empty string equals false
  • D. bool(false), because '0' is a truthy string
Correct answer: A. The string '0' is one of PHP's falsy values, so with loose comparison ('==') it equals false, yielding true.
Which PDO practice most effectively prevents SQL injection when inserting user-supplied data?
  • A. Escaping the input with addslashes() before concatenating it into the query
  • B. Using prepared statements with bound parameters via placeholders ✓
  • C. Wrapping the query inside a try/catch block
  • D. Setting PDO::ATTR_ERRMODE to PDO::ERRMODE_EXCEPTION
Correct answer: B. Prepared statements with bound parameters separate SQL code from data, which is the correct defense against SQL injection.
In PHP, what is the practical difference between `require` and `include`?
  • A. require caches the file, while include re-reads it on every call
  • B. require only works for classes, while include works for any file
  • C. On failure to find the file, require raises a fatal error and halts, while include only emits a warning and continues ✓
  • D. There is no difference; they are exact aliases
Correct answer: C. A failed require produces a fatal E_COMPILE_ERROR and stops the script, whereas a failed include emits an E_WARNING and execution continues.
You want to store a user's password in the database. Which approach is correct in modern PHP?
  • A. Hash it with md5() before storing
  • B. Encrypt it with a symmetric key using openssl_encrypt()
  • C. Use password_hash() to store and password_verify() to check ✓
  • D. Store it as-is but only over an HTTPS connection
Correct answer: C. password_hash() (bcrypt/argon2 by default) with password_verify() is the standard, salted, slow-hash approach for password storage.
What does the `===` operator do that `==` does not?
  • A. It compares both value and type without type juggling ✓
  • B. It compares object references instead of object contents
  • C. It performs a case-insensitive string comparison
  • D. It automatically casts both operands to strings first
Correct answer: A. The identity operator '===' requires both the value and the type to match, avoiding the type coercion that '==' performs.
In a Composer project, what is the primary purpose of the `composer.lock` file?
  • A. It lists the minimum PHP version required to run the project
  • B. It records the exact installed versions of dependencies so installs are reproducible ✓
  • C. It stores encrypted credentials for private package repositories
  • D. It defines PSR-4 autoloading namespace mappings
Correct answer: B. composer.lock pins the exact resolved dependency versions, so `composer install` reproduces an identical dependency set across environments.
A form is submitted with `method="post"`. In the PHP script, which superglobal reliably contains the submitted field values?
  • A. $_GET
  • B. $_REQUEST only
  • C. $_POST ✓
  • D. $_SERVER
Correct answer: C. Values sent via an HTTP POST body are populated into the $_POST superglobal.
What is the correct way to iterate over an associative array while accessing both keys and values?
  • A. for ($i = 0; $i < count($arr); $i++) { echo $arr[$i]; }
  • B. foreach ($arr as $key => $value) { echo "$key: $value"; } ✓
  • C. while (list($value) = each($arr)) { echo $value; }
  • D. foreach ($arr as $value => $key) { echo "$key: $value"; }
Correct answer: B. The `foreach ($arr as $key => $value)` syntax is the standard way to access both keys and values of an associative array.
Your PHP script returns a blank white page in production but works on your machine. What is the most appropriate first step to diagnose it?
  • A. Increase memory_limit in php.ini to the maximum
  • B. Restart the web server to clear the opcode cache
  • C. Check the PHP error log (or temporarily enable display_errors in dev) to find the fatal error ✓
  • D. Switch the database from MySQL to PostgreSQL
Correct answer: C. A blank page typically means a fatal error with display_errors off; the error log reveals the actual cause, which is the correct first diagnostic step.
What does the following return: `array_map(fn($n) => $n * 2, [1, 2, 3])`?
  • A. [2, 4, 6] ✓
  • B. 12
  • C. [1, 2, 3, 2, 4, 6]
  • D. 6
Correct answer: A. array_map applies the callback to each element, returning a new array [2, 4, 6].

Hard round 10 questions

A worker process ingests a 4 GB CSV row-by-row and its RSS climbs steadily even though each row is processed and discarded. The code builds an intermediate object graph where a parent node holds children and each child holds a `$parent` back-reference. `unset()` on the parent at the end of each loop does not reclaim memory. What is the most accurate explanation and fix?
  • A. The reference count never reaches zero because of the parent-child cycle, so only PHP's cyclic GC frees it; call gc_collect_cycles() periodically or break the back-reference before unset ✓
  • B. unset() is asynchronous in CLI SAPI, so memory is freed only at request shutdown; switch to FPM to force immediate release
  • C. The strings are interned in the Zend string table permanently; disable string interning via opcache to free them
  • D. Copy-on-write duplicated each row into a new zval that PHP cannot track; add & to the foreach to force a reference and avoid the copy
Correct answer: A. Circular references keep refcounts above zero so simple refcounting can't free them; only the cyclic collector (or manually breaking the cycle) reclaims the memory.
Given `$result = true and false; var_dump($result);` — what is printed and why?
  • A. bool(false), because 'and' evaluates both operands before assigning
  • B. bool(true), because '=' has higher precedence than 'and', so $result is assigned true first ✓
  • C. bool(true), because 'and' short-circuits on the truthy left operand
  • D. NULL, because mixing 'and' with '=' is a parse error that yields null
Correct answer: B. '=' binds tighter than the low-precedence 'and', so PHP parses it as `($result = true) and false`, assigning true to $result.
Two traits `A` and `B` both define `public function log()`. A class uses both: `use A, B;` with no conflict-resolution block. What happens?
  • A. A fatal error is raised at compile time due to the unresolved method collision unless you use insteadof/as ✓
  • B. B::log() silently wins because the later trait in the use list takes precedence
  • C. A::log() silently wins because traits are applied left-to-right and the first binding is kept
  • D. Both methods coexist and PHP dispatches based on the argument signature (overloading)
Correct answer: A. An unresolved trait method collision is a fatal error; you must disambiguate explicitly with `insteadof` and optionally alias with `as`.
A base class has `public static function create(): static { return new static(); }` and `self` is used elsewhere. A subclass `Admin extends User` calls `Admin::create()`. Which statement about late static binding is correct here?
  • A. Because `new static()` uses the runtime-resolved called class, it returns an Admin instance; `new self()` would have returned a User instance ✓
  • B. Both `static` and `self` resolve to Admin because static methods always bind to the calling class
  • C. `new static()` returns a User instance because static binding is resolved at compile time in the defining class
  • D. The call fails because `static` cannot be used as a return type in a factory method
Correct answer: A. Late static binding makes `new static()` use the runtime-called class (Admin), whereas `self` is bound at definition time to User.
You need to stream 10 million rows from a query result, transforming each, without loading them all into memory, AND you must be able to pause/resume the producer cooperatively from a scheduler that also drives network I/O. Which primitive combination is the best fit in PHP 8.1+?
  • A. A Generator for lazy row production, driven by a Fiber so the scheduler can suspend it during I/O waits ✓
  • B. A single Fiber that returns an array of all 10M transformed rows at the end
  • C. An ArrayObject wrapping the full result set with an SplHeap for ordering
  • D. pcntl_fork() one child per row and join them via SplQueue
Correct answer: A. Generators give lazy, memory-efficient iteration while Fibers add the cooperative suspend/resume needed for a scheduler juggling I/O, so combining them fits both constraints.
An API endpoint decrements stock: `SELECT qty FROM items WHERE id=?` then `UPDATE items SET qty=qty-1 WHERE id=?` in a transaction under READ COMMITTED. Under high concurrency stock occasionally goes negative. Which fix most directly eliminates the race with minimal contention?
  • A. Use `SELECT ... FOR UPDATE` on the row inside the transaction, or make the UPDATE conditional with `WHERE qty > 0` and check affected rows ✓
  • B. Switch the isolation level to READ UNCOMMITTED so readers see the latest uncommitted decrement
  • C. Wrap both statements in a PHP-level flock() on a lock file shared across web servers
  • D. Add an index on qty so the UPDATE locks fewer rows
Correct answer: A. Row-level pessimistic locking with FOR UPDATE (or an atomic conditional UPDATE guarded by affected-row count) closes the check-then-act gap that lets qty go negative.
After migrating a codebase to PHP 8, a previously reliable check `if (strpos($haystack, $needle))` starts treating some matches as misses and, in another spot, `"0" == "0.0"` changed behavior. Which pair of PHP 8 changes explains these?
  • A. strpos returning 0 for a front-of-string match was always falsy (a long-standing bug in the code), and in PHP 8 two numeric strings like "0" and "0.0" compare equal with == because both are numeric ✓
  • B. strpos now returns false instead of 0 for position 0, and "0"=="0.0" now compares as strings and is false
  • C. strpos now throws a TypeError on non-string needles, and numeric-string == comparison was removed in PHP 8
  • D. strpos now returns null on no match, and == now always does strict comparison
Correct answer: A. The strpos-at-0 falsy bug predates PHP 8, while PHP 8's comparison change makes number-vs-string use string context only when the string is non-numeric — "0" and "0.0" are both numeric so they compare numerically and are equal.
You pass an object into a function and also an array: `function f($obj, $arr){ $obj->x = 1; $arr[] = 9; }` called as `f($o, $a);`. After the call, what is true about `$o` and `$a`?
  • A. $o->x is 1 (the object handle was copied but points to the same instance), and $a is unchanged (arrays are passed by value) ✓
  • B. Both $o and $a reflect the mutations because everything in PHP is passed by reference
  • C. Neither reflects the mutation because objects and arrays are both copied on pass
  • D. $a gains element 9 but $o->x is unchanged because objects are deep-copied on call
Correct answer: A. PHP passes an object's handle by value so the same underlying instance is mutated, while arrays are true value types (copy-on-write) so appends inside the function don't affect the caller's array.
Under OPcache with `opcache.jit=tracing` enabled, a request-heavy JSON API sees almost no speedup after enabling JIT, while a Mandelbrot-style CPU loop got ~4x. What is the most accurate reason?
  • A. Typical web request workloads are dominated by I/O and short-lived function calls, so JIT (which mainly accelerates long CPU-bound arithmetic loops) yields little benefit; opcode caching already removed the compile cost ✓
  • B. JIT only activates when opcache.jit_buffer_size is 0, so the API's non-zero buffer disabled it
  • C. JIT cannot compile code that uses arrays, so the JSON API fell back to the interpreter
  • D. JIT requires preloading via opcache.preload; without it the tracing JIT never fires for any workload
Correct answer: A. JIT primarily helps CPU-bound numeric loops; I/O-bound request code gains little because opcode caching already eliminated recompilation and the hot path isn't arithmetic.
A Laravel endpoint renders 50 posts and their authors' names via `$posts = Post::all(); foreach($posts as $p){ echo $p->author->name; }`. The DB log shows 51 queries. Which change fixes the N+1 problem while keeping one query per relation?
  • A. Use eager loading: `Post::with('author')->get()`, which issues one query for posts and one WHERE IN query for all authors ✓
  • B. Add `->lazy()` to the query so relations are resolved in a single JOIN automatically
  • C. Wrap the loop in a transaction so the 50 author queries are batched into one round trip
  • D. Enable query result caching so the 50 author queries are served from cache on first request
Correct answer: A. Eager loading with `with('author')` collects the foreign keys and fetches all authors in one WHERE IN query, turning 51 queries into 2.

Prep for another role

Questions are original, written and independently verified for HireHire's role interview quizzes. They reflect the kind of knowledge PHP Developer interviews test, not any specific company's questions. HireHire maps live tech & IT jobs across India, updated regularly. Last updated: July 2026.