PHP interviews often begin by establishing which PHP you have written, because the language changed substantially between version 5 and version 8. Expect questions on loose versus strict comparison, prepared statements and injection defence, session handling behind a load balancer, interfaces, abstract classes and traits, Composer and PSR-4 autoloading, and the PHP 8 features that changed how modern code is written. Security and performance judgement feature throughout. The questions below cover all of it.
Behavioural Questions
1. Tell me about a PHP project you have worked on. What was your role and what was challenging?
Note: PHP interviews often start here because the ecosystem spans everything from a 2005 procedural codebase to modern Laravel. The interviewer is placing you on that spectrum.
Cover:
- What it was and its scale. Traffic, database size, number of developers. Say whether it was greenfield or an existing system, because they are entirely different skills.
- The stack, honestly. PHP version, framework or none, database, and how it was deployed. If it was legacy procedural PHP, say so — having survived that is a credential, not an embarrassment.
- The hard part. Good candidates: an upgrade from PHP 5 to 7 or 8, N+1 queries under load, a session or caching problem that only appeared on multiple servers, or introducing tests to a codebase that had none.
- The outcome, with a number. Response time, error rate, or deploy frequency before and after.
2. How do you approach working on a legacy PHP codebase with no tests and outdated practices?
The answer they are screening against is "rewrite it". Show you can improve incrementally.
- Get it running locally and under version control first. Surprisingly often that is the real first task.
- Add characterisation tests around what you must touch. Not a full suite — just enough to know whether your change altered behaviour. These tests document what the code does, which may differ from what anyone thinks it does.
- Fix security before style. SQL built by string concatenation, unescaped output, and an unsupported PHP version are urgent. Inconsistent brace placement is not.
- Upgrade PHP one version at a time, using a static analyser such as PHPStan or Rector to find what will break before it does.
- Introduce structure at the edges. Composer for autoloading, then move logic into classes as you touch it, rather than in a dedicated refactor nobody will approve.
Note: The strangler pattern is worth naming: route new features through a modern framework alongside the legacy application and migrate pages gradually, so the old system shrinks instead of being replaced in one risky push.
3. Describe a time you found and fixed a performance problem in a PHP application.
Tell it as measurement, diagnosis, fix, verification — and be specific about which layer was slow, because the causes are different.
- How you found it. Slow request logs, an APM tool such as New Relic or Blackfire, or the MySQL slow query log. Say what the actual number was.
- Where the time was going. In most PHP applications it is the database, not PHP. Profiling with Xdebug or Blackfire tells you rather than leaving you to guess.
- The fix. Common high-value ones: eliminating an N+1 query by eager loading, adding a missing index, caching an expensive computation in Redis, enabling OPcache, or moving a slow third-party call into a queued job.
- What it bought you, and the monitoring you added so it would not silently regress.
Note: The most impressive version of this story involves resisting a premature optimisation — measuring first and discovering the bottleneck was somewhere nobody expected. Interviewers hear "I optimised the loop" constantly and "I measured and the loop was irrelevant" rarely.
4. How do you handle code review and maintain quality standards in a PHP team?
Split it into what machines enforce and what humans discuss — the distinction is the point.
Automated, in CI:
- PHP-CS-Fixer or PHP_CodeSniffer against PSR-12, so formatting is never a review comment.
- PHPStan or Psalm at a level the team agrees, raised over time. Static analysis catches a large class of bug in a dynamically typed language.
- PHPUnit with the suite required to pass before merge.
Human review then focuses on what tools cannot judge: whether the approach fits the system, whether edge cases are handled, whether the naming will make sense in a year, and whether there is a security implication.
How to give review well: mark clearly which comments block the merge and which are suggestions; ask questions where you might lack context; keep pull requests small enough to actually read.
Note: If you have introduced any of this to a team that lacked it, lead with that. Raising a PHPStan level from 0 to 5 across a codebase is a concrete, credible achievement.
5. PHP has a mixed reputation. How do you decide when it is the right tool, and how do you keep your skills current?
Answer this directly rather than getting defensive — the interviewer usually wants to see whether you evaluate tools honestly.
Where PHP is genuinely a strong choice: request-response web applications, content-driven sites, and anything where a mature framework and a huge hosting ecosystem matter. PHP 8 with JIT, real type declarations, enums, and readonly properties is a very different language from PHP 5, and Laravel and Symfony are as capable as their equivalents elsewhere.
Where you would pick something else: long-lived connections and real-time work, where Node or Go fit the model better; heavy numerical or machine-learning work, where the Python ecosystem is decisive; CPU-bound services where a compiled language pays off.
How you stay current: the RFCs on the PHP wiki, which show where the language is going before it arrives; release notes each version; and the Laravel or Symfony changelogs. Most usefully, running a static analyser at a high level teaches you a lot about your own assumptions.
Note: Saying "the PHP of 2012 deserved its reputation and the PHP of today does not" is a fair, credible position — and demonstrates you know what changed.
Technical Questions
1. What is the difference between == and === in PHP, and how does type juggling work?
=== compares type and value. == converts the operands to a common type first, following rules that produce genuinely surprising results.
PHP 8 fixed the worst of it. Comparing a number to a non-numeric string used to cast the string to a number, so 0 == "foo" was true — a real authentication bypass in the wild. Since PHP 8, the number is cast to a string instead, so that comparison is now false.
What still catches people:
"1" == "01"istrue— two numeric strings are compared as numbers."10" == "1e1"istrue, for the same reason.null == false,0 == false, and"" == falseare alltrue.[] == falseistrue.
The practical rule: use === by default. It is not a style preference — loose comparison against user input has caused real security vulnerabilities, particularly when comparing tokens or hashes. For those, use hash_equals(), which is also timing-safe.
Note: Be ready for in_array() as a follow-up. Its third parameter enables strict comparison, and without it in_array(0, ['a','b']) behaved surprisingly on older versions.
2. How do you prevent SQL injection in PHP, and what is the difference between PDO and MySQLi?
Prepared statements with bound parameters are the answer. The SQL structure is sent to the database separately from the values, so a value can never be interpreted as SQL.
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ? AND status = ?');
$stmt->execute([$email, $status]);
$user = $stmt->fetch();Escaping functions such as mysqli_real_escape_string are a weaker fallback — they depend on the correct connection charset and are easy to apply inconsistently. Never build SQL with string concatenation or interpolation of user input.
PDO versus MySQLi:
- PDO supports twelve database drivers, so your data layer is portable. It has a consistent object API, named placeholders, and can throw exceptions on error.
- MySQLi is MySQL-only but exposes MySQL-specific features such as asynchronous queries.
PDO is the usual recommendation unless you need something MySQL-specific.
Note: Two configuration points matter. Set PDO::ATTR_ERRMODE to ERRMODE_EXCEPTION so failures are not silently ignored, and set PDO::ATTR_EMULATE_PREPARES to false so the database does the preparing rather than the driver — emulated prepares are not equivalent protection.
3. What is the difference between include, require, include_once and require_once?
All four pull in another PHP file. They differ on two axes: what happens on failure, and whether repetition is allowed.
include— emits a warning if the file is missing and continues execution.require— emits a fatal error and halts.include_once/require_once— the same, but PHP tracks which files it has already loaded and skips a repeat.
Which to use: require for anything the script cannot run without — a configuration file, a class definition, a database connection. include only for genuinely optional content, such as a sidebar template. Continuing after a missing class definition produces a cascade of confusing errors instead of one clear one.
The _once variants prevent "cannot redeclare function" fatal errors when a file is reachable through more than one path. They carry a small overhead because PHP must check the loaded-file list.
Note: The right answer in modern PHP is that you rarely write any of these. Composer's PSR-4 autoloader loads classes on demand, so a single require of vendor/autoload.php replaces hundreds of manual includes.
4. How do sessions work in PHP, and what problems appear when you run multiple servers?
session_start() looks for a session id in a cookie. If there is none, PHP generates one, sets the cookie, and creates a store; if there is, PHP loads the matching data into $_SESSION. The data lives on the server; only the id travels to the browser.
By default the store is a file in the server's temp directory. That is precisely what breaks behind a load balancer: a user's first request writes a session file on server A, their second request goes to server B, which finds nothing, and they appear logged out at random.
The fixes, in order of preference:
- A shared session store — Redis or Memcached via
session.save_handler. Fast, and scales to any number of servers. This is the standard answer. - A database handler — slower but durable and easy to inspect.
- Sticky sessions at the load balancer — the least good option, because it undermines even load distribution and loses sessions when an instance is replaced.
Security points worth adding: call session_regenerate_id(true) immediately after login to prevent session fixation, and set the cookie httponly, secure, and samesite.
5. Explain object-oriented programming in PHP: interfaces, abstract classes and traits.
PHP gives you three ways to share structure, and the interview question is usually about choosing between them.
- Interface — a contract of method signatures with no implementation. A class may implement many. Use it to say what a class can do, so unrelated classes can be used interchangeably.
- Abstract class — a partial implementation that cannot be instantiated. It may hold state, constructors, and concrete methods alongside abstract ones. A class may extend only one. Use it for an "is-a" relationship where subclasses genuinely share behaviour.
- Trait — a block of methods copied into a class at compile time. It is PHP's answer to the lack of multiple inheritance. Use it for behaviour reused across classes with no natural common ancestor — a logging helper, a timestamp helper.
How to choose: prefer an interface for the contract and a trait for shared implementation, and reach for an abstract class when there is a real hierarchy. "Program to an interface" is what makes code testable, because you can substitute a fake.
Note: Traits have a real cost — they hide dependencies, since a trait method may rely on a property the using class must provide. Overusing them produces classes whose behaviour is scattered across five files.
6. How do you handle errors and exceptions in PHP 8, and what changed from earlier versions?
PHP historically had two parallel systems — traditional errors and exceptions — and PHP 7 unified them. Most fatal errors now throw Error, and both Error and Exception implement Throwable.
try {
$result = $service->process($input);
} catch (ValidationException $e) {
return $this->badRequest($e->getMessage());
} catch (Throwable $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
return $this->serverError();
} finally {
$this->cleanup();
}What matters in practice:
- Catch
Throwable, notException, when you want a genuine catch-all —TypeErrorandDivisionByZeroErrorareErrors and will slip pastException. - Define your own exception types.
PaymentDeclinedExceptionlets callers handle one failure differently from another; throwing a genericExceptionforces string matching on messages. - Set an exception handler and an error handler at the application's entry point so nothing escapes unlogged, and convert warnings into
ErrorExceptionso they cannot be ignored. - Never show a stack trace to a user.
display_errorsoff in production,log_errorson.
Note: PHP 8 also made many previously-silent warnings into errors — accessing an undefined array key, for example — which is why an upgrade surfaces bugs that were always there.
7. What is the difference between GET and POST, and how do you handle form input securely in PHP?
GET sends data in the URL query string. It is visible, bookmarkable, logged by servers and proxies, cached, and length-limited. Use it for retrieving — searches, filters, pagination — because repeating it must be safe.
POST sends data in the request body. It is not in the URL, not cached by default, and effectively unlimited in size. Use it for anything that changes state.
Note: POST is not secure by itself. Both are plaintext without HTTPS. The reason not to put a password in a GET request is that it lands in browser history and server logs, not that POST is encrypted.
Handling input securely — the layers:
- Validate on the server, always. Client-side validation is a convenience, not a control. Check type, length, format, and range with
filter_varor a validation library. - Escape at the point of use, differently for each context. Prepared statements for SQL;
htmlspecialchars($v, ENT_QUOTES, 'UTF-8')for HTML output;json_encodefor JavaScript. There is no single sanitising function that makes input universally safe. - Use a CSRF token on every state-changing form, compared with
hash_equals. - For file uploads, verify the real MIME type rather than trusting the extension, cap the size, rename the file, and store it outside the web root.
8. What is Composer and how does autoloading work in PHP?
Composer is PHP's dependency manager. composer.json declares what you need with version constraints; composer.lock records the exact versions actually installed, so every environment gets an identical dependency tree.
Note: Commit composer.lock for an application; do not commit it for a library. And composer install honours the lock file while composer update resolves fresh versions and rewrites it — using update on a deploy is a common and dangerous mistake.
Autoloading means classes load on demand instead of being manually included. When PHP encounters an unknown class, it calls the registered autoloader, which maps the class name to a file path and includes it.
{
"autoload": {
"psr-4": { "App\\": "src/" }
}
}Under PSR-4, App\Service\Payment resolves to src/Service/Payment.php. Namespace separators become directory separators, and the class name must match the filename exactly — including case, which is why code works on a case-insensitive Mac and breaks on a Linux server.
In production, run composer install --no-dev --optimize-autoloader, which builds a static class map so no filesystem lookup is needed per class.
9. How would you improve the performance of a slow PHP application?
Measure first — the bottleneck is rarely where people assume. Profile with Blackfire or Xdebug, and check the database's slow query log before touching PHP at all.
Then work through the layers in order of typical payoff:
- The database, almost always first. Fix N+1 queries by eager loading, add the missing index that
EXPLAINreveals, and select only the columns you need rather thanSELECT *. - OPcache. It caches compiled bytecode so PHP does not re-parse every file on every request. Enabling it is often the single largest win, and it is off by default in some configurations.
- Application caching. Redis or Memcached for expensive computations, rendered fragments, and configuration. Have a clear invalidation strategy before you add it.
- Move slow work out of the request. Emails, report generation, image processing, and third-party API calls belong in a queue.
- HTTP-level caching. Correct
Cache-ControlandETagheaders, a CDN for static assets, and gzip or brotli compression. - PHP itself. Upgrading from PHP 7 to 8 is a meaningful free improvement, and preloading and JIT help specific workloads.
Note: Micro-optimisations such as single versus double quotes are noise. Say so — it demonstrates a sense of proportion.
10. What new features did PHP 7 and PHP 8 introduce that changed how you write code?
The language changed substantially, and interviewers use this to date your experience.
PHP 7 brought:
- Scalar type declarations and return types, plus
declare(strict_types=1)to make them enforced rather than coerced. - The null coalescing operator
??, replacing theisset() ? :dance. - The spaceship operator
<=>for comparison functions. - Errors as exceptions, so fatal errors became catchable.
- Roughly double the performance of PHP 5.6, which is why the upgrade was worth doing on its own.
PHP 8 brought:
- Constructor property promotion — declaring and assigning properties in the signature, which removes a great deal of boilerplate.
- Named arguments, so
createUser(admin: true)is readable at the call site. - Union types (
int|string), andreadonlyproperties in 8.1. - Enums in 8.1 — real type-safe enumerations instead of class constants.
- Match expressions, which return a value and use strict comparison with no fall-through.
- The nullsafe operator
?->, and attributes for structured metadata instead of docblock parsing. - JIT compilation, which helps CPU-bound work more than typical web requests.
Note: The most useful thing to say is which of these changed your habits. Promoted constructors, enums, and readonly properties together make immutable value objects genuinely pleasant, which was awkward before.





