Skip to content

PHP Developer interview questions

100 real questions with model answers and explanations for Senior PHP Developer candidates.

See a PHP Developer resume example

Practice with flashcards

Spaced repetition · Hunter Pass

Questions

hexagonallaravelorm

I would extract one checkout use case behind inbound and outbound ports while the existing controllers remain HTTP adapters.

  • Place order invariants in domain objects, orchestration in an application service, and define ports for payment, order persistence, and event publication.
  • Laravel adapters implement those ports with Eloquent, Stripe, and RabbitMQ, while the service container wires them only at the composition edge.
  • I would migrate one endpoint first and require its domain tests to run without booting Laravel or connecting to three external systems.

Why interviewers ask this: The interviewer is checking an incremental hexagonal design with enforceable dependency direction.

ormsymfony

I would replace framework types at one high-change use-case boundary with an input command and an explicit result.

  • The controller maps Request data into a validated command, while the application service never imports HttpFoundation.
  • The repository maps Doctrine entities to domain objects or read projections, so lazy-loading proxies cannot escape into serialization.
  • A dependency check in CI rejects Symfony and Doctrine imports from domain and application namespaces after each migrated use case.

Why interviewers ask this: A strong answer turns layers into compiler-checkable boundaries instead of directory names.

I would create separate bounded-context models because the two Order concepts have different invariants and lifecycles.

  • Billing owns a payable order and publishes OrderPaid with a stable order ID, amount, currency, and event version.
  • Fulfillment consumes that contract through an anti-corruption mapper and owns shipment state without importing billing entities or tables.
  • Contract tests prove both contexts understand version 1, while a context map records ownership and the one-way integration.

Why interviewers ask this: The interviewer is evaluating DDD boundaries from conflicting business meanings rather than class duplication.

transactionsaggregation

I would make inventory for one SKU the consistency boundary and enforce the reservation invariant in one atomic database transition.

  • The aggregate root rejects a reservation above available quantity and uses a version column or conditional update such as available >= requested.
  • The application service loads and saves one SKU aggregate in one transaction, while order and notification work happen outside that lock.
  • A concurrent test starts 20 requests for the final 3 units and requires exactly 3 accepted units with no negative balance.

Why interviewers ask this: A strong answer connects the domain invariant to an atomic persistence mechanism under concurrency.

pricing

I would replace floats at the pricing boundary with an immutable Money value object using integer minor units and currency.

  • Construction rejects mixed currency and invalid precision, while add, subtract, tax, and allocation methods preserve rounding rules explicitly.
  • Persistence stores amount_minor and ISO currency, and API adapters map decimal strings without a float conversion.
  • Property tests allocate 1 cent through 10,000 cents across up to 20 lines and require the parts to sum exactly to the original amount.

Why interviewers ask this: The interviewer is checking value-object design that eliminates a concrete numerical invariant failure.

ormhttpvalidation

I would keep transaction orchestration in the application service and move policy and transport decisions to their proper owners.

  • The Refund aggregate or policy object decides eligibility and amount from domain values without Response, EntityManager, or vendor exceptions.
  • The application service loads the aggregate, invokes refund behavior, commits once, and records an outbox event in the same transaction.
  • The controller maps domain outcomes to HTTP status and error documents, while a unit test covers the 12-step use case with repository and clock fakes.

Why interviewers ask this: A strong answer separates domain decisions, use-case coordination, persistence, and HTTP mapping.

queriesmonolithphp

I would give each module an owned schema surface and a small public application API before considering separate deployments.

  • Cross-module reads use typed query ports or published projections, while direct imports of another module's entities and repositories are forbidden.
  • Transactions remain local to one module; workflows spanning modules use application orchestration or committed events with explicit consistency.
  • Deptrac and database-access tests fail CI on forbidden namespaces or table use, starting with the five highest-change modules.

Why interviewers ask this: The interviewer is checking modular-monolith boundaries that constrain both code and data coupling.

queriesormtypes

I would replace persistence-shaped methods with operations named for the domain decisions the caller needs.

  • An OrderRepository might expose get(OrderId), add(Order), and a dedicated overdue-order read projection instead of arbitrary criteria.
  • QueryBuilder, ORM entities, and flush remain in the Doctrine adapter; transaction ownership stays in the application boundary.
  • Contract tests run the same repository cases against the Doctrine adapter and an in-memory fake, including missing and concurrent-version behavior.

Why interviewers ask this: A strong answer protects domain semantics instead of disguising a generic ORM gateway as a repository.

redisauthcaching

I would authorize before any tenant-sensitive cache lookup, measure the whole operation, and retry only the remote adapter that is safe to repeat.

  • The outer metrics decorator records denied, hit, miss, retry, and final latency without exposing secret labels.
  • Authorization runs before a cache keyed by tenant, actor scope, resource ID, and representation version, preventing cross-tenant hits.
  • Retry wraps only the idempotent remote call, not authorization or the complete use case, and a test verifies at most three provider attempts.

Why interviewers ask this: The interviewer is evaluating decorator ordering through security, observability, and idempotency consequences.

schemaormserialization

I would publish a versioned domain fact containing stable business identifiers and values, not an ORM snapshot.

  • Version 1 carries event_id, order_id, customer_id, occurred_at, total minor units, and currency with documented required fields.
  • The aggregate records the event after its invariant passes, and a transactional outbox stores it with the order commit.
  • Consumer contract tests decode historical version-1 fixtures, while schema evolution adds fields compatibly or introduces a new event version.

Why interviewers ask this: A strong answer treats domain events as durable integration contracts independent of database layout.

opcachephp

I would size OPcache from measured used memory, wasted memory, interned strings, file count, and release growth rather than a default template.

  • With 190 MB compiled code, I would start at 256 MB for roughly 25% headroom and set max_accelerated_files above 25,000, such as 40,000.
  • Monitor opcache_get_status for cache_full, wasted_percentage, misses, and restart counters under the production release artifact.
  • A 60-minute load test must produce zero capacity restarts and keep hit rate above 99.9% before the new settings ship.

Why interviewers ask this: The interviewer is checking quantitative OPcache sizing from bytecode and file evidence.

validationdeploymentopcache

I would deploy one immutable artifact and gracefully replace every PHP process rather than rely on file timestamp checks.

  • Composer optimized autoload and framework caches are built once in CI inside a versioned release directory.
  • The symlink or container image switches atomically, then PHP-FPM and long-running workers drain and restart against the same release ID.
  • Health checks report release ID and load representative classes; traffic expands only when 100% of workers serve the new artifact.

Why interviewers ask this: A strong answer connects disabled revalidation to process lifecycle and atomic immutable deployment.

opcacherollback

I would keep only the measured hot preload set if 12 ms materially improves the latency objective after its memory and deployment cost.

  • Blackfire call counts identify framework and domain files loaded on nearly every request, instead of preloading the entire vendor tree.
  • Compare worker density, master shared memory, cold restart time, and p95 latency with no preload, a 40 MB set, and the current 420 MB set.
  • I would choose the smallest set meeting the target and require graceful master restart in both deploy and rollback runbooks.

Why interviewers ask this: The interviewer is evaluating preload as a measured latency-memory-deployment trade-off.

postgresapilaravel

I would expect little request-latency gain because JIT cannot optimize time spent waiting on the database or network.

  • Benchmark the same traffic with OPcache alone and with JIT, recording CPU, p50, p95, p99, memory, and throughput.
  • A separate CPU-bound pricing simulation with 50 million arithmetic operations may benefit and is the right workload for JIT evaluation.
  • I would leave JIT off for the API unless it improves p95 or CPU by at least the agreed threshold without reducing worker density.

Why interviewers ask this: A strong answer limits JIT expectations to CPU-bound PHP execution and demands representative evidence.

latencyprofilingendpoints

I would profile a representative slow checkout and optimize the dominant inclusive cost before introducing another cache.

  • Compare wall time, CPU, I/O wait, call count, and memory for the same payload against a 780 ms baseline profile.
  • If 430 ms comes from 101 Doctrine queries, fix the access pattern and index first; caching the final response would hide write-dependent correctness.
  • Save the scenario with assertions for query count below 10 and p95 below 250 ms so the improvement remains a regression gate.

Why interviewers ask this: The interviewer is checking measurement-led optimization and correct interpretation of profiler call graphs.

profiling

I would use Xdebug only on one controlled, identical input and interpret call proportions rather than its distorted wall time.

  • Cachegrind shows call counts, inclusive cost, and self cost, revealing whether formatting, hydration, or one algorithm dominates.
  • I would confirm the selected hotspot with a lower-overhead timer or Blackfire run before claiming a production gain.
  • The optimized report must beat the original 42-second baseline on three Xdebug-free runs with identical output checksums.

Why interviewers ask this: A strong answer accounts for profiler observer overhead and verifies against an uninstrumented baseline.

I would stream bounded result batches and write output incrementally instead of materializing 2 million rows.

  • Use keyset batches or an unbuffered PDO cursor where the driver permits, selecting only the columns required by the export.
  • Process 1,000 to 5,000 rows, release ORM state with clear when applicable, and flush the output stream after each batch.
  • A full fixture must stay below 200 MB peak, preserve row count and checksum, and avoid a long transaction that blocks cleanup.

Why interviewers ask this: The interviewer is evaluating PHP memory behavior, database cursors, and bounded export design.

postgresphp-fpmphp

Memory permits about 68 children, but I would cap the pool near 56 because the database connection budget is the tighter constraint.

  • The memory ceiling is floor(12,288/180)=68, leaving no reason to configure more children than downstream concurrency can serve.
  • Reserve four database connections for queue or maintenance work and verify whether every child can hold one persistent connection.
  • Load-test 56 children with the FPM listen queue, RSS, CPU, database waits, and p99 latency, then tune from measured saturation.

Why interviewers ask this: A strong answer performs the memory calculation and then respects the smaller downstream connection budget.

I would use dynamic mode with a small warm floor so idle memory remains bounded without paying an 80-process cold-start burst.

  • Configure enough start_servers and min_spare_servers for the observed burst ramp, while max_children still follows memory and database limits.
  • Static fits a continuously busy predictable pool, while ondemand remains useful for truly rare endpoints that tolerate spawn latency.
  • A burst test after ten idle minutes must keep p95 below the admin SLO and avoid spawn storms or more than the approved idle RSS.

Why interviewers ask this: The interviewer is checking process-manager choice against a concrete traffic and cold-start pattern.

data-structures

The workload needs 1.67 busy workers, so I would start with four workers: three for 50% headroom and one failure reserve.

  • Arrival rate is 8.33 jobs/s and 8.33×0.2 seconds gives 1.67 concurrent workers at average service time.
  • I would validate p95 job duration and downstream connection use because a 1-second tail can make the average calculation unsafe.
  • A soak test must keep oldest-job age below the target after one worker is stopped and show the database below 70% of its connection budget.

Why interviewers ask this: A strong answer calculates queue concurrency and adds explicit headroom and failure capacity.

Locked questions

  • 21

    RabbitMQ redelivers an invoice job after the worker commits the invoice but dies before ack. How do you prevent a second invoice?

  • 22

    A failed webhook job retries every second, reaches 25,000 attempts, and keeps hitting a provider returning 422. How do you classify and route it?

    webhooks
  • 23

    Eight workers process account events out of order, so BalanceUpdated version 42 arrives before version 41. How do you preserve the required ordering?

    concurrency
  • 24

    A Symfony Messenger worker must process 20,000 messages while staying below 180 MB. What reset and recycling policy do you design?

    designsymfonyconcurrency
  • 25

    Producers enqueue 1,200 jobs/s, but a payment provider safely accepts only 800 calls/s. How do you apply backpressure?

    backpressure
  • 26

    A Laravel Octane application serves 20 tenants from persistent workers. How should CurrentTenant be scoped and reset?

    laravelasync
  • 27

    A Symfony container has 9,000 services and cache warmup takes 75 seconds. What would you inspect before replacing autowiring?

    symfonycontainerscaching
  • 28

    A Laravel API has tenant resolution, authentication, rate limiting, transactions, and response caching middleware. What order do you choose?

    middlewarelaravelapi
  • 29

    A Laravel endpoint loads 100 orders and executes 301 SQL queries because each order accesses customer and items. How do you fix it without overfetching?

    sqlqueriesendpoints
  • 30

    A Doctrine import of 100,000 entities takes 14 minutes and reaches 1.3 GB before flush completes. How do you change the UnitOfWork pattern?

    orm
  • 31

    A Symfony controller validates a discount with attributes, but the same invalid discount enters through Messenger. Where should the rule live?

    validationsymfony
  • 32

    A Laravel model observer sends email inside the database transaction, adding 900 ms and sending mail even when a later write rolls back. What do you change?

    databasetransactionslaravel
  • 33

    A Symfony kernel listener runs on every request, performs two database queries, and is needed only for 8 admin routes. How do you narrow it?

    databasequeriessymfony
  • 34

    A PDO query filters tenant_id and status, orders by created_at and id, returns 50 rows, but scans 4 million rows. What index do you test?

    indexesqueriespdo
  • 35

    Page 2,000 of an orders API uses LIMIT 50 OFFSET 99,950 and takes 1.4 seconds. How do you redesign pagination?

    pagination
  • 36

    Two PHP requests read available credit as 100 and each spend 80 under READ COMMITTED. Which database control would you use?

    databasephp
  • 37

    A PDO report accepts up to 500 account IDs and currently concatenates them into an IN clause. How do you keep it safe and efficient?

    pdo
  • 38

    After creating an order, the next GET sometimes returns 404 from a replica lagging by 800 ms. How do you provide read-after-write behavior?

    replication
  • 39

    A Doctrine dashboard hydrates 60,000 entities to calculate five totals and takes 3.8 seconds. What query shape do you use?

    queriesorm
  • 40

    A Swoole service must handle 2,000 concurrent requests whose database calls take 120 ms. What client and pool design do you choose?

    designasyncdatabase
  • 41

    A ReactPHP websocket service must keep 10,000 connections responsive while a JSON export consumes 700 ms of CPU. Where do you run the export?

    reactresponsivewebsockets
  • 42

    A team wraps blocking curl_exec in a Fiber and expects 500 HTTP calls to run concurrently. Why will it not scale?

    reacthttpconcurrency
  • 43

    A long-running Octane worker will serve 20,000 requests alternating between tenants and locales. Which state must reset at every boundary?

    async
  • 44

    A Swoole application opens one database connection per coroutine, so 800 concurrent requests exhaust PostgreSQL. How do you size the pool?

    databasepostgresconcurrency
  • 45

    A POST /payments endpoint is retried after a 10-second timeout and occasionally charges twice. What API contract do you add?

    endpointsresilience
  • 46

    A bulk API accepts 10,000 customer updates in one request and holds an FPM worker for 45 seconds. How would you redesign it?

    api
  • 47

    Three APIs return validation failures in incompatible shapes, forcing clients to write route-specific parsers. What error contract do you standardize?

    validationapi
  • 48

    A GraphQL orders query requests 200 orders with customer and items and produces 401 SQL queries. How do you control it?

    sqlqueriesgraphql
  • 49

    A popular Redis key expires every hour and 600 PHP requests simultaneously recompute a 2-second report. How do you stop the stampede?

    redisphp
  • 50

    One Redis Cluster key receives 90,000 reads/s and saturates a single shard while the other five shards are idle. How do you redesign the cache?

    shardingrediscaching
  • 51

    Laravel Octane workers start at 120 MB RSS and reach 900 MB after 15,000 requests, while memory_get_usage reports only 260 MB. How do you investigate?

    laravelasyncmemory
  • 52

    A Swoole service adds about 8 MB RSS per 1,000 requests on all 64 workers, and growth correlates with tenant switches. What do you inspect first?

    async
  • 53

    A Symfony Messenger worker grows from 95 MB to 710 MB after 10,000 jobs, and Doctrine's identity map contains 180,000 entities. What do you change?

    ormsymfony
  • 54

    A ReactPHP websocket process keeps 12,000 connections, but RSS grows by 300 MB whenever 2,000 clients disconnect. How do you find the retained data?

    reactwebsocketsconcurrency
  • 55

    A PDF worker's RSS rises from 140 MB to 1.2 GB, but PHP heap stays near 170 MB and gc_collect_cycles changes nothing. What is your next step?

    data-structuresphp
  • 56

    An Octane worker retains 40 copies of a request object after a route runs 40 times, and the memory graph points through event-listener closures. How do you remove the cycle?

    closuresmemoryasync
  • 57

    Production recycles queue workers every 500 jobs to hide a 2 MB-per-job leak, but restarts now consume 18% CPU. How do you balance mitigation and diagnosis?

    data-structures
  • 58

    Traffic rises 35%, PHP-FPM p99 grows from 180 ms to 2.4 seconds, max_children is reached, and the listen queue reaches 320. What do you check first?

    data-structuresphpphp-fpm
  • 59

    A PHP-FPM host starts swapping after pm.max_children was raised from 60 to 110, and p99 latency jumps to 6 seconds. How do you recover?

    zero-to-onelatencyphp-fpm
  • 60

    A deploy doubles PHP-FPM replicas from 20 to 40, and PostgreSQL immediately reaches its 800-connection limit. How do you contain and fix the fan-out?

    postgresreplicationfan-out
  • 61

    An external tax API slows from 120 ms to 4 seconds, holding 90% of FPM workers and blocking unrelated catalog requests. What do you do?

    api
  • 62

    CPU reaches 100% on every FPM host after a payload change, while database and Redis latency remain normal. One endpoint's p99 rises to 9 seconds. How do you localize it?

    databaseredisendpoints
  • 63

    Autoscaling adds 30 PHP pods during a flash sale, but p99 still climbs from 250 ms to 3 seconds and database CPU reaches 96%. What is your decision?

    databasescalingphp
  • 64

    A queue backlog grows from 5,000 to 900,000 jobs in 40 minutes while workers remain 95% busy. How do you choose between scaling and throttling?

    backlogscalingdata-structures
  • 65

    After a serializer release, database QPS jumps from 4,000 to 28,000 and checkout errors rise to 9%; traces show a new nested relation query per item. How do you handle it?

    databasequeriessoft-skills
  • 66

    A quick N+1 fix eager-loads six relations for 500 products, reducing queries from 3,001 to 7 but raising memory from 180 MB to 1.4 GB. What do you change?

    n+1queriesmemory
  • 67

    A PostgreSQL settlement query rises from 180 ms to 6.5 seconds and writes 4 GB of temporary files per call after result volume triples. How do you respond?

    queriespostgres
  • 68

    A MySQL query suddenly chooses the wrong index after one tenant grows from 1 million to 40 million rows, although SQL did not change. What do you inspect?

    sqlindexesqueries
  • 69

    An UPDATE by primary key takes 12 seconds despite an index, and database monitoring shows lock waits. How do you diagnose it?

    databaseindexesprimary-keys
  • 70

    A new customer search allows two-character `%term%` matches on 80 million rows, saturates a read replica, and pushes API p99 to 11 seconds. What do you do?

    replicationapi
  • 71

    A SQL injection is confirmed in a report endpoint that concatenates sort and filter values, and logs show 37 suspicious requests. What do you do first?

    sqlendpointsinjection
  • 72

    A stored XSS payload appears in 4,200 support tickets after a Blade template uses raw output. How do you contain and remediate it?

    templatingxss
  • 73

    For a 12-hour exposure window, a public endpoint calls unserialize on a signed cookie, and a leaked signing key makes a gadget-chain attack possible. How do you respond?

    cookiesendpoints
  • 74

    An image-import feature can fetch arbitrary URLs, and audit logs show 9 requests to the cloud metadata address. How do you close the SSRF path?

  • 75

    A Laravel debug page exposed database and API credentials to 260 authenticated users for 18 minutes. What is your response?

    databaseapilaravel
  • 76

    A tenant can access another tenant's invoice by changing /invoices/781 to /invoices/782, and 64 cross-tenant reads appear in logs. How do you fix the authorization model?

    auth
  • 77

    Composer audit reports a critical remote-code-execution vulnerability in a package loaded by 35 PHP services. How do you coordinate the technical response?

    vulnerabilitiescomposerphp
  • 78

    A cache lock has a 1-second TTL, recomputation takes 4 seconds, and one expiry launches seven refreshers whose late results overwrite newer data. How do you repair the incident?

    incidentscaching
  • 79

    A deploy invalidates 200,000 product keys at once, dropping Redis hit rate from 96% to 8% and saturating PostgreSQL. How do you recover?

    postgresredisdeployment
  • 80

    Deleting one 180 MB Redis value blocks a shard for 1.7 seconds every five minutes and times out unrelated sessions. How do you diagnose and remove the large-key hazard?

    shardingredissessions
  • 81

    Two requests miss the cache, compute versions 41 and 42, then the slower version 41 overwrites 42 for ten minutes. How do you prevent stale fill races?

    caching
  • 82

    Redis becomes unavailable for six minutes, and automatic cache fallback drives PostgreSQL connections from 180 to 790 of 800. What do you do?

    postgresrediscaching
  • 83

    A response cache omits tenant ID from its key and serves 17 invoices to the wrong tenants. How do you contain and correct the cache design?

    designcaching
  • 84

    After deployment, 120 of 240 FPM workers call a new method while the rest throw undefined method because OPcache still serves the old class. How do you recover?

    opcacherestdeployment
  • 85

    A rollback restores old files, but 24 workers with the new OPcache preload keep crashing. What is missing from the rollback?

    schedulingrollbackopcache
  • 86

    A release contains the new application code but an old vendor directory on 12 of 80 hosts, causing class-not-found errors. How do you fix the artifact pipeline?

    ci-cdartifactsprocurement
  • 87

    A rolling deploy leaves 30% of queue workers on the old message class and 70% on the new schema, producing deserialization failures. How do you restore compatibility?

    schemadeploymentdata-structures
  • 88

    A new Laravel release reads a column before the migration reaches all database shards, causing 500 errors on 18% of tenants. What do you roll back?

    databasemigrationssharding
  • 89

    A production release on 60 instances still uses yesterday's Laravel config and routes even though the files are correct. How do you diagnose the stale caches?

    laravelcachingconfig
  • 90

    A PHP 5.6 monolith has 1.2 million lines, 240 Composer packages, and only 18% test coverage. How do you plan the first PHP 8 migration stage?

    migrationsmonolithcoverage
  • 91

    A direct PHP 5.6 to 8.3 test run produces 14,000 failures across language changes and dependencies. How do you reduce the change surface?

    dependenciesphp
  • 92

    The first service extracted from a monolith writes customer data in both databases, and 0.6% of records already diverge. How do you regain one authority?

    databasemonolith
  • 93

    A Symfony major upgrade touches 160 bundles, queue serialization, security rules, and 900 routes. How do you stage it without freezing delivery?

    serializationsymfonydata-structures
  • 94

    Adding declare(strict_types=1) to 3,000 legacy files changes numeric-string behavior in payment inputs. How do you migrate safely?

  • 95

    A nightly PHP cron processes 4 million rows for six hours, overlaps the next run, and cannot resume after failure. How do you migrate it to workers?

    concurrencyphpcron
  • 96

    A homegrown PHP authentication module must be replaced, but 3 million active sessions use its cookie format. What migration path do you choose?

    authsessionscookies
  • 97

    A junior engineer fixes mixed-code deploys by disabling OPcache on all 80 hosts, increasing CPU by 45%. How do you mentor the correction?

    mentoringdeploymentopcache
  • 98

    A mid-level engineer proposes raising pm.max_children from 70 to 180 to fix a 2-second p99, but the database has only 120 spare connections. How do you review it?

    zero-to-onedatabase
  • 99

    An engineer closes 3 SQL injection payloads by rejecting quotes with a regex, but the endpoint still concatenates sort and filter input. How do you guide the fix?

    sqlendpointsinjection
  • 100

    An engineer hides a 101-query endpoint behind a 30-minute Redis cache, but stale prices now cause 2% checkout corrections. How do you coach the redesign?

    queriesredisendpoints