PHP Interview Questions: 15 for Hiring Managers, Scored
Short answer
Useful PHP interview questions test understanding rather than memory: loose versus strict comparison, enums and readonly classes, PHP 8.3 and 8.4 features, safe database access, sessions, Composer, testing and debugging. Show candidates real code, ask why rather than what, and score every interview against the same sheet so you can compare candidates fairly.
According to the JetBrains State of PHP 2025 survey, 32 percent of PHP developers don’t write tests at all, and 42 percent use no code quality tool whatsoever, not even PHPStan or a code style fixer. Those aren’t fringe groups. Each is somewhere between a third and two-fifths of the market.
That shapes how you interview. The biggest differences between PHP candidates usually aren’t about syntax. They’re about how someone works. The questions below are chosen to show both.
How to structure the interview
Good technical interviews follow a fixed script. Only then can you compare two candidates fairly. Improvised questions end up measuring how much you liked the person.
- 5 minutesIntroduce the product, stack and role.
- 20 minutesWalk through the take-home task; ask for the reasoning behind decisions.
- 25 minutesLanguage, database and security questions using real code.
- 15 minutesHow they work: tests, debugging, deploys, a real incident.
- 10 minutesThe candidate's questions.
The take-home task up front saves a lot of interview time. Our guide to the PHP coding test explains what to set and why you should pay for it.
Language fundamentals: four questions
1. What does this print, and why?
$ids = ['7', '08', '1e1'];
var_dump(in_array(10, $ids)); // bool(true)
var_dump(in_array(8, $ids)); // bool(true)
var_dump(in_array(8, $ids, true)); // bool(false)
var_dump(array_search('7', [7, '7'])); // int(0)
The strong answer explains that in_array() and array_search() compare loosely by default, that numeric strings such as '08' and '1e1' are compared as numbers, and that the third argument switches to strict comparison. Bonus points for mentioning that PHP 8 changed how numbers compare with non-numeric strings, so 0 == 'abc' is now false.
Then ask where this has bitten them. Someone who’s chased a permissions bug caused by a loose in_array() won’t forget the third parameter again.
2. What happens on the last line?
declare(strict_types=1);
enum Currency: string
{
case EUR = 'EUR';
case GBP = 'GBP';
case USD = 'USD';
}
final readonly class Money
{
public function __construct(
public int $minorUnits,
public Currency $currency,
) {}
public function add(Money $other): self
{
if ($other->currency !== $this->currency) {
throw new LogicException('Currency mismatch');
}
return new self($this->minorUnits + $other->minorUnits, $this->currency);
}
}
$price = new Money(1999, Currency::GBP);
$price->minorUnits = 2499;
It throws an Error: readonly properties can’t be modified after initialisation. Follow up with two questions. Why store money as an integer in minor units rather than a float? And what’s the difference between Currency::from('JPY') and Currency::tryFrom('JPY')? The first throws a ValueError, the second returns null. People who’ve built billing code answer both without hesitating.
3. What does declare(strict_types=1) actually change?
Scalar arguments are no longer silently coerced, so passing "5" to an int parameter throws a TypeError. Good candidates add that the setting applies per file and affects calls made from that file, not calls into it. That detail trips up plenty of experienced people, so don’t fail anyone over it. Just note who knows.
4. Why prefer match over switch?
match compares strictly, returns a value, needs no break and throws an UnhandledMatchError when nothing matches. With enum-heavy code that last point is the real win: add a new case, forget to handle it, and you find out immediately.
Modern PHP: three questions on 8.3 to 8.5
JetBrains found 89 percent of respondents on PHP 8.x. Newer features show whether someone follows the language. Nobody has to know everything, but curiosity is a good signal.
5. What’s new here, and what does it protect against?
interface RateLimited
{
const int PER_MINUTE = 60;
public function limiterKey(): string;
}
final class PartnerApiClient implements RateLimited
{
const int PER_MINUTE = 120;
#[\Override]
public function limiterKey(): string
{
return 'partner-api';
}
}
Two PHP 8.3 features: typed class constants and the #[\Override] attribute. The typed constant stops a subclass from quietly redefining PER_MINUTE as a string. The attribute makes PHP raise an error if the interface method is renamed and limiterKey() no longer overrides anything. Both catch mistakes at compile time that used to surface in production.
6. What does this property do?
final class Customer
{
public function __construct(
public string $firstName,
public string $lastName,
) {}
public string $fullName {
get => "{$this->firstName} {$this->lastName}";
}
}
It’s a property hook, new in PHP 8.4. Because the hook never touches its own backing value, $fullName is a virtual property: it’s computed on every read and can’t be written. The official explanation is in the PHP manual on property hooks. Strong candidates also mention asymmetric visibility, such as public private(set), which arrived in the same release.
7. Have you used the pipe operator yet?
PHP 8.5, released in November 2025, added |>, which passes the value on its left into the callable on its right. “I’ve read about it but haven’t used it” is a perfectly good answer. Confidently explaining it wrong is the only bad one.
Databases, sessions, dependencies: five questions
8. How do you sort by a column the user chooses?
Placeholders can’t stand in for column names, so this question exposes people who think prepared statements solve everything.
$allowed = ['created_at', 'email', 'last_login_at'];
$sort = in_array($_GET['sort'] ?? '', $allowed, true) ? $_GET['sort'] : 'created_at';
$stmt = $pdo->prepare("SELECT id, email FROM users WHERE status = :status ORDER BY {$sort} DESC");
$stmt->execute(['status' => 'active']);
The value goes through a placeholder; the column name goes through an allow-list, compared strictly. Candidates who suggest escaping the column name with a quoting function haven’t thought it through.
9. How do you store passwords?
password_hash() with PASSWORD_DEFAULT, checked with password_verify(). Anyone who mentions MD5 or SHA-1 isn’t ready for a role that touches user data. Good answers also mention password_needs_rehash() to upgrade old hashes at the next login.
10. What should happen to the session at login?
Call session_regenerate_id(true) after a successful login to prevent session fixation, and set cookies with HttpOnly, Secure and a sensible SameSite value. Framework developers may say Laravel or Symfony does this for them. That’s true. Ask them what exactly it does.
11. When do you run composer update versus composer install?
install reads composer.lock and installs exactly those versions; it’s what CI and production should run. update resolves new versions within your constraints and rewrites the lock file. Someone who runs composer update on a production server has a story to tell you, so ask for it.
12. Where does XSS come from, and how do you prevent it?
Printing user input into HTML without escaping. The fix is escaping on output: htmlspecialchars() in plain PHP, automatic in Blade and Twig. The follow-up separates people: when does auto-escaping get bypassed? In Twig, the raw filter; in Blade, the unescaped echo syntax.
How they work: three open questions
These have no model answer. Listen for specifics.
- Show me a test you wrote recently. What did it protect against?
- A page that loaded in 300 milliseconds now takes five seconds in production. What do you do in the first hour?
- Tell me about a bug you shipped. How did it get through, and what changed afterwards?
Question 14 shows whether someone debugs systematically. JetBrains found 59 percent of PHP developers mostly debug with var_dump-style output and 39 percent use a debugger such as Xdebug. Both are fine. What matters is whether they measure first, with logs or a profiler or by counting queries, before guessing.
Question 15 is the most honest one in the interview. Anyone who claims never to have shipped a bug has either little experience or little self-awareness.
For senior roles, add architecture questions; our article on senior PHP developer skills has a set. For framework-specific roles, the Laravel vs Symfony comparison suggests what to probe.
A scoring sheet that keeps interviews comparable
Right after each interview, score each area from 0 to 2 before you talk to anyone else. That way the loudest interviewer doesn’t set everyone’s opinion.
| Area | 0 | 1 | 2 |
|---|---|---|---|
| Language core | gaps on types and comparison | solid day to day | explains edge cases |
| Modern PHP | stuck on PHP 7 | uses enums and readonly | knows 8.3/8.4 and has opinions |
| Security | vague | standard measures solid | spots exceptions and traps |
| Tests and quality | doesn’t test | tests when told | tests by habit, uses static analysis |
| Debugging | guesses | step by step | measures first, knows tools |
| Communication | short answers, no reasons | reasons when asked | reasons unprompted, asks back |
For a mid-level role, 8 out of 12 is a solid result. For senior roles, no area should score 0. These thresholds are a suggestion, not a standard; adjust them to your role.
Questions you can skip
Some classics appear on every list and tell you little. “What’s the difference between include and require?” Anyone can look that up in ten seconds. Same for the parameter order of strpos() or array_search(). That tests memory, not skill.
Whiteboard algorithm puzzles with no link to web work also say little about PHP development. If you want to test algorithmic thinking, take a real problem from your own product.
If you don’t have the time to build a proper process, an outside team can handle the technical assessment or the development itself, for example a studio like OnlineBuilders. If you’re hiring yourself, it all starts earlier, with a clear PHP developer job description.
Copy the scoring sheet into a shared document now, pick six to eight questions that fit your role, and have a colleague answer them as a dry run. You’ll know straight away whether they fit into 25 minutes.
Questions people ask
How long should a PHP technical interview be?
Sixty to ninety minutes is enough if candidates completed a short take-home task beforehand. Longer rarely means better, just more tiring. Keep the last ten minutes for their questions.
Should I ask candidates to code live?
You can, but live coding while being watched mostly measures nerves. Talking through prepared code, or through the candidate's own take-home solution, shows how they think without the artificial pressure.
How should I handle AI tools in interviews?
JetBrains reports that 80 percent of PHP developers use AI assistants regularly, so a blanket ban isn't realistic for take-home work. Ask how the candidate checks generated code, and have them explain a section of their own solution.
Which questions should I never ask?
Questions about age, family plans, religion, nationality or disability are off-limits in the UK under the Equality Act 2010 and are risky in most US states and EU countries. Stick to the work.