PHP Backend Developer Top 100 Interview Questions
100 PHP backend developer interview questions with structured answers, comparison tables, flow diagrams, worked calculations and runnable code. Covers modern typed PHP, the request lifecycle, HTTP and APIs, security, PDO and databases, caching, queues, testing, performance, deployment and system design, from the basics to production incidents.
Questions 81-100 of 100
Q81.How would you design the checkout flow of a PHP e-commerce application?
Make checkout a sequence of explicit, recoverable steps: validate the cart, reserve stock, create the order, take payment and confirm. Each step must be safe to retry, and the slow or unreliable parts, such as payment confirmations and emails, are handled asynchronously.
flowchart TD A["Validate cart, prices and address"] --> B["Create pending order with idempotency key"] B --> C["Reserve stock atomically"] C --> D["Redirect to payment or confirm payment intent"] D --> E["Payment webhook received and verified"] E --> F["Mark order paid, commit"] F --> G["Queue emails, invoices and fulfillment"] D -->|"Payment fails or times out"| H["Release stock reservation"]Key decisions:
- Prices recalculated on the server, never trusted from the client.
- Idempotency keys on order creation, so double submissions create one order.
- Atomic stock reservation with conditional updates, plus expiry for abandoned checkouts.
- Payment status from verified webhooks, not only from the browser redirect, which users can close.
- State machine for orders, with allowed transitions such as pending to paid to shipped.
- Money in integer minor units with explicit rounding.
- Asynchronous side effects through a queue, recorded in the same transaction as the order change.
Q82.How would you design an image upload and processing pipeline for a PHP application?
Let clients upload directly to object storage with short-lived signed URLs, record the upload in the database and process images in background workers. PHP web workers never handle large file bytes, so they stay available for normal requests.
flowchart LR A["Client requests upload URL"] --> B["PHP issues signed URL with limits"] B --> C["Client uploads directly to object storage"] C --> D["Storage event or client confirmation"] D --> E["Queue processing job"] E --> F["Worker validates, re-encodes and resizes"] F --> G["Store variants and update the record"] G --> H["Content delivery network serves images"]Design points:
- Signed URLs restrict size, content type and expiry, and use generated object names.
- Validation in the worker: check the real file type from its content, reject oversized dimensions and re-encode images, which removes embedded scripts and metadata such as location.
- Variants: thumbnails and sizes generated once and cached.
- States: uploaded, processing, ready and failed, so the interface can show progress.
- Retries and dead-letter handling for processing failures.
- Cleanup: delete orphaned uploads that were never confirmed.
- Privacy: private images served through short-lived signed download URLs.
Q83.How would you design caching for a product catalog with very heavy read traffic?
Layer caches from the edge inward: cache public pages and API responses at a content delivery network, cache computed product data in a shared store and keep the database as the source of truth. Invalidate from the source when products change, so caches stay fresh without short lifetimes everywhere.
flowchart LR A["Users"] --> B["Content delivery network: pages and images"] B --> C["PHP application"] C --> D["Shared cache: product data"] D -->|Miss| E["Database"] F["Product updated in admin"] --> G["Invalidation event"] G --> B G --> DDesign points:
- Cache keys by product and version, including language and currency, so variants never mix.
- Edge caching for anonymous traffic, with personalized parts, such as cart counts, loaded separately.
- Event-driven invalidation: an update publishes an event that purges the edge cache and deletes the shared cache entry.
- Stampede protection when popular entries expire, by letting one request rebuild while others serve stale data briefly.
- Warm the cache for the most popular products after deployments or purges.
- Prices and stock often need shorter lifetimes or live lookups, since stale values cause real problems.
Measure hit rates at each layer, and set alerts when they drop.
Q84.How would you design a multi-tenant PHP application?
Identify the tenant on every request, from the domain, a path segment or the authenticated user, store it in a request-scoped context and make every data access filter by it automatically. Choose the isolation model by compliance requirements, tenant size and operational cost.
Shared tables with a tenant column Database or schema per tenant Separate deployment per tenant Every row carries a tenant ID Each tenant's data lives in its own database or schema Each tenant gets its own application and database Cheapest and simplest to scale to many tenants Stronger isolation and easier per-tenant backups Strongest isolation and customization Risk: one missing filter leaks data Risk: running migrations across many databases Risk: high cost and operational effort Making isolation reliable:
- Resolve the tenant once in middleware, and reject requests where the tenant cannot be determined.
- Enforce filters centrally, in a repository base class, query builder scope or database row-level security.
- Include the tenant in cache keys, file storage paths, queue jobs and logs.
- Test isolation explicitly: automated tests that try to read another tenant's data.
- Protect against noisy tenants with per-tenant rate limits and quotas.
Q85.How would you implement an LRU cache in PHP using arrays?
PHP arrays preserve insertion order, so they can serve as both the lookup table and the recency list. On every read, remove the key and insert it again, so it moves to the end; when the cache is full, remove the first key, which is the least recently used.
Operations:
- get: if the key exists, unset it, set it again and return the value.
- set: unset any existing entry, add the new value at the end and remove
array_key_firstif the count exceeds the capacity.
Points to know:
- Integer-like string keys become integers, which still works but can surprise when keys are printed or merged.
- Each worker has its own cache, and in the request-per-process model it disappears at the end of the request, so this structure suits per-request memoization or long-running workers.
- Use a shared cache for data that must survive across requests.
- Capacity by count is simple; very different value sizes may need a memory-based limit.
Example
After reading
a, addingcevictsb.<?php declare(strict_types=1); final class LruCache { private array $items = []; public function __construct(private int $capacity) {} public function get(string $key): mixed { if (!array_key_exists($key, $this->items)) { return null; } $value = $this->items[$key]; unset($this->items[$key]); $this->items[$key] = $value; return $value; } public function set(string $key, mixed $value): void { unset($this->items[$key]); $this->items[$key] = $value; if (count($this->items) > $this->capacity) { unset($this->items[array_key_first($this->items)]); } } public function keys(): array { return array_keys($this->items); } } $cache = new LruCache(2); $cache->set('a', 1); $cache->set('b', 2); $cache->get('a'); $cache->set('c', 3); echo json_encode($cache->keys()), "\n"; // ["a","c"] echo var_export($cache->get('b'), true), "\n"; // NULLQ86.How would you group and summarize records with PHP arrays?
Loop once over the records, build a summary keyed by the grouping field, accumulate counts and totals and then sort the summary for presentation. A single pass is clearer and faster than repeated filtering for each group.
Steps:
- Initialize each group's entry the first time it is seen, with
??=. - Add counts and totals, and track nested breakdowns, such as revenue per product within each region.
- Sort groups with
uasortand a comparison using the spaceship operator<=>. - Pick the top item within each group with
arsortandarray_key_first.
When to do it in SQL instead: for large data,
GROUP BYin the database is far more efficient, since only the summary travels to PHP. Use PHP grouping for data already in memory, such as API results, or small sets.Typing tip: for anything beyond simple reports, convert rows into small value objects, so field names are checked and the summary code is easier to read.
Example
One pass builds per-region totals and per-product revenue, then sorts regions by revenue.
<?php declare(strict_types=1); $orders = [ ['region' => 'north', 'product' => 'lamp', 'qty' => 2, 'price' => 450], ['region' => 'south', 'product' => 'desk', 'qty' => 1, 'price' => 7200], ['region' => 'north', 'product' => 'desk', 'qty' => 1, 'price' => 7200], ['region' => 'north', 'product' => 'lamp', 'qty' => 1, 'price' => 450], ['region' => 'south', 'product' => 'pen', 'qty' => 10, 'price' => 20], ]; $summary = []; foreach ($orders as $o) { $region = $o['region']; $line = $o['qty'] * $o['price']; $summary[$region] ??= ['orders' => 0, 'revenue' => 0, 'products' => []]; $summary[$region]['orders']++; $summary[$region]['revenue'] += $line; $summary[$region]['products'][$o['product']] = ($summary[$region]['products'][$o['product']] ?? 0) + $line; } uasort($summary, fn (array $a, array $b): int => $b['revenue'] <=> $a['revenue']); foreach ($summary as $region => $s) { arsort($s['products']); printf("%s: orders=%d revenue=%d top=%s\n", $region, $s['orders'], $s['revenue'], array_key_first($s['products'])); } // Output: // north: orders=3 revenue=8550 top=desk // south: orders=2 revenue=7400 top=desk- Initialize each group's entry the first time it is seen, with
Q87.How would you create URL slugs from text in PHP, including non-English text?
Lowercase the text with multibyte functions, replace every run of characters that are not letters, combining marks or digits with a single hyphen and trim hyphens from the ends. Using Unicode character classes in the regular expression keeps letters from every script.
Details that matter:
- The
umodifier makes the regular expression work on UTF-8 characters instead of bytes. \p{L},\p{M}and\p{N}match letters, combining marks and digits in any language; leaving out\p{M}breaks scripts that use combining vowel signs.mb_strtolowerlowercases non-English letters correctly.- Uniqueness: check the database, and append a short suffix when a slug is already taken.
- Transliteration to plain ASCII is optional; it needs the internationalization extension and loses meaning for some languages.
Stability: once published, a slug is part of a URL that users and search engines remember; keep old slugs as redirects when titles change.
Example
Spaces and punctuation become single hyphens, accented letters stay and a script with combining marks keeps its characters.
<?php declare(strict_types=1); function slugify(string $text): string { $lower = mb_strtolower(trim($text)); return trim(preg_replace('/[^\p{L}\p{M}\p{N}]+/u', '-', $lower), '-'); } foreach (['Senior PHP Developer', ' Data Engineer (Remote)! ', 'Café & Bar', 'नमस्ते दुनिया'] as $title) { echo slugify($title), "\n"; } // Output: // senior-php-developer // data-engineer-remote // café-bar // नमस्ते-दुनिया- The
Q88.How would you implement retries with exponential backoff and jitter in PHP?
Wrap the operation in a loop that catches retryable exceptions, waits for an exponentially growing delay with random jitter and gives up after a fixed number of attempts, rethrowing the last exception. Injecting the sleep function makes the logic easy to test without real waiting.
Design points:
- Retry only transient failures, such as timeouts and 503 responses, by catching specific exception types.
- Exponential delay:
base × 2^(attempt − 1), capped at a maximum. - Jitter: randomize part of each delay with
random_int, so many workers do not retry together. - Total time budget: in web requests, stop retrying long before the request's own timeout.
- Idempotency: retry only operations that are safe to repeat, or use idempotency keys.
- Background jobs are often a better place for retries than web requests, since they can wait longer.
Example
The operation succeeds on its third attempt with delays inside the expected ranges, and a permanent failure surfaces its exception.
<?php declare(strict_types=1); function retry(callable $operation, int $attempts, int $baseMs, ?callable $sleep = null): mixed { $sleep ??= fn (int $ms) => usleep($ms * 1000); for ($attempt = 1; ; $attempt++) { try { return $operation($attempt); } catch (RuntimeException $e) { if ($attempt >= $attempts) { throw $e; } $delay = $baseMs * 2 ** ($attempt - 1); $sleep(intdiv($delay, 2) + random_int(0, intdiv($delay, 2))); } } } $waits = []; $result = retry( function (int $attempt): string { if ($attempt < 3) { throw new RuntimeException("temporary failure $attempt"); } return "ok on attempt $attempt"; }, 5, 100, function (int $ms) use (&$waits): void { $waits[] = $ms; }, ); echo $result, "\n"; echo count($waits), " waits within bounds: ", var_export($waits[0] >= 50 && $waits[0] <= 100 && $waits[1] >= 100 && $waits[1] <= 200, true), "\n"; try { retry(fn () => throw new RuntimeException('service down'), 3, 10, fn () => null); } catch (RuntimeException $e) { echo $e->getMessage(), "\n"; } // Output: // ok on attempt 3 // 2 waits within bounds: true // service downQ89.How would you build a small dependency injection container with autowiring in PHP?
Use reflection to read a class's constructor parameters, resolve each typed dependency recursively from the container and create the object. Bindings map interfaces to concrete classes, and created services are stored, so each one is shared.
How autowiring works:
- Look up a binding for the requested type, or use the type itself if it is a concrete class.
- Read the constructor's parameters with
ReflectionClass. - For each class or interface type, resolve it from the container.
- For scalar parameters, use the default value, or fail with a clear message naming the parameter.
- Create the instance with the resolved arguments, and cache it.
Production considerations:
- Compile or cache the resolution in production, since reflection on every request is wasteful.
- Support factories for services that need configuration values or complex setup.
- Detect circular dependencies and report them clearly.
- Keep the container out of business code: only the composition root asks it for services.
Example
The container binds an interface, autowires constructor dependencies, uses a scalar default and shares instances.
<?php declare(strict_types=1); final class Container { private array $instances = []; private array $bindings = []; public function bind(string $abstract, string $concrete): void { $this->bindings[$abstract] = $concrete; } public function get(string $id): object { if (isset($this->instances[$id])) { return $this->instances[$id]; } $class = $this->bindings[$id] ?? $id; $reflection = new ReflectionClass($class); $args = []; foreach ($reflection->getConstructor()?->getParameters() ?? [] as $param) { $type = $param->getType(); if ($type instanceof ReflectionNamedType && !$type->isBuiltin()) { $args[] = $this->get($type->getName()); } elseif ($param->isDefaultValueAvailable()) { $args[] = $param->getDefaultValue(); } else { throw new LogicException('cannot resolve parameter ' . $param->getName() . " of $class"); } } return $this->instances[$id] = $reflection->newInstanceArgs($args); } } interface Clock { public function now(): string; } final class FixedClock implements Clock { public function now(): string { return '2026-03-01T10:00:00Z'; } } final class OrderRepository {} final class OrderService { public function __construct(public OrderRepository $orders, public Clock $clock, public int $pageSize = 20) {} } $container = new Container(); $container->bind(Clock::class, FixedClock::class); $service = $container->get(OrderService::class); echo get_class($service->clock), " ", $service->pageSize, " ", $service->clock->now(), "\n"; var_dump($service->orders === $container->get(OrderRepository::class)); // Output: // FixedClock 20 2026-03-01T10:00:00Z // bool(true)Q90.How would you implement a rate limiter for PHP workers that share no memory?
Store counters in shared storage and update them atomically in a single statement. A fixed-window limiter counts hits per key per time window; an upsert that increments and returns the new count avoids race conditions between workers.
Algorithm:
- Compute the current window:
intdiv(now, window length). - Insert a row for the key and window with one hit, or increment the existing row, and return the new count, all in one statement.
- Allow the request if the count is within the limit; otherwise reject it with status 429 and a
Retry-Afterheader. - Delete old windows periodically.
Choosing the store: an in-memory cache server is the usual choice for speed, using its atomic increment with an expiry; a database table works for moderate traffic and keeps the example simple.
Limits of fixed windows: a client can send twice the limit around a window boundary; sliding windows or token buckets smooth this when it matters.
Example
Three requests per minute are allowed; the fourth and fifth are blocked, and a new window allows requests again.
<?php declare(strict_types=1); $db = new PDO(getenv('DB_DSN'), options: [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); $db->exec('CREATE TABLE rate_limits (rl_key TEXT NOT NULL, rl_window INTEGER NOT NULL, hits INTEGER NOT NULL, PRIMARY KEY (rl_key, rl_window))'); function allow(PDO $db, string $key, int $limit, int $windowSeconds, int $now): bool { $stmt = $db->prepare( 'INSERT INTO rate_limits (rl_key, rl_window, hits) VALUES (:key, :window, 1) ON CONFLICT (rl_key, rl_window) DO UPDATE SET hits = hits + 1 RETURNING hits' ); $stmt->execute(['key' => $key, 'window' => intdiv($now, $windowSeconds)]); return (int) $stmt->fetchColumn() <= $limit; } $results = []; foreach ([0, 10, 20, 30, 59, 60] as $offset) { $results[] = allow($db, 'ip:203.0.113.5', 3, 60, 1_000_020 + $offset) ? 'allow' : 'block'; } echo implode(',', $results), "\n"; // allow,allow,allow,block,block,allow- Compute the current window:
Q91.How do you modernize a legacy PHP codebase safely?
Modernize incrementally while the application keeps running: add tests around critical behavior, introduce structure at the edges and replace old patterns one area at a time. Big-bang rewrites of large PHP applications usually take far longer than planned and reintroduce fixed bugs.
A practical sequence:
- Upgrade PHP to a supported version, fixing deprecations along the way.
- Add a dependency manager and autoloading, replacing manual
requirechains. - Add characterization tests that record the current behavior of important pages and endpoints, before changing them.
- Introduce a front controller that routes new URLs to new code while old scripts keep working.
- Extract business logic from mixed templates and scripts into typed, tested classes.
- Replace unsafe patterns: string-built SQL with prepared statements, unescaped output with escaped templates and globals with injected dependencies.
- Add static analysis with a baseline, so new code is clean and old issues shrink over time.
- Automate deployment with atomic releases.
Keep measuring: error rates and performance before and after each change, so improvements are visible and regressions are caught early.
Q92.How do you keep a PHP project's dependencies secure?
Know what you depend on, update it regularly, check it automatically for known vulnerabilities and limit the risk any one package can cause.
Practices:
- Commit the lock file and install from it in every environment, so versions are exactly what was tested.
- Audit in continuous integration: the dependency manager can check installed packages against vulnerability advisories and fail the build on known issues.
- Update on a schedule, such as weekly for patch releases, with automated pull requests and tests.
- Review new dependencies before adding them: maintenance activity, security history, size and transitive dependencies.
- Remove unused packages, since every package is code you must keep patched.
- Be careful with install scripts, which run code during installation, and disable them where not needed.
- Mirror packages for builds, so removed or tampered packages cannot break or poison deployments.
- Update PHP and extensions too, since the runtime itself receives security fixes.
Also consider abandoned packages: a package with no recent releases may not get fixes; plan to replace it before a vulnerability forces a rushed change.
Q93.How do you internationalize a PHP application?
Separate all user-visible text from code, format numbers, dates and currencies with locale-aware functions and store everything as UTF-8. The internationalization extension provides formatting and plural rules for every locale.
Key pieces:
- Translation catalogs: message keys mapped to translations per locale, loaded through a translation function in templates and code.
- Placeholders and plurals: messages with named placeholders and plural rules, because languages have different plural forms, such as one, few and many.
- Formatting: number, currency and date formatters for the user's locale, never hand-written formats.
- Locale selection: from the user's profile, then the browser's language preference, with a default.
- Time zones: store UTC and display in the user's time zone.
- Collation and search: sort and compare text with locale-aware rules where order matters.
Practical tips:
- Do not build sentences by concatenating translated fragments; word order differs between languages.
- Leave room in layouts, since translations can be much longer.
- Support right-to-left scripts in templates where needed.
- Keep translations in version control, and check for missing keys in continuous integration.
Q94.How do you design a REST API in PHP that is easy to evolve?
Design resources around business concepts, keep a consistent style for URLs, errors, pagination and filtering and make changes additive, so existing clients keep working as the API grows.
Consistent conventions:
- Resource-oriented URLs:
/orders,/orders/{id},/orders/{id}/items, with standard methods. - Status codes used correctly: 201 for creation with a
Locationheader, 204 for successful deletion, 4xx for client errors and 5xx for server errors. - One error format with machine-readable types and field-level validation messages.
- Cursor-based pagination with a maximum page size.
- Explicit filters and sorting from allowlists.
- Idempotency keys for operations that create or charge.
Evolving safely:
- Additive changes: new optional fields and new endpoints, never renamed or removed ones.
- Tolerant clients and servers: ignore unknown fields where safe, and document it.
- Versioning only for genuinely breaking changes, with old versions running during migration.
- Deprecation process: announce, add warning headers, track usage and remove only when traffic is gone.
Separate API data from storage: map database rows to response objects explicitly, so schema changes do not leak into the API.
- Resource-oriented URLs:
Q95.How do you handle time-consuming work triggered by a web request in PHP?
Return a response to the user quickly and do the slow work elsewhere. PHP offers a simple option, finishing the response before continuing, and a robust one, handing the work to a queue processed by background workers.
Finish the response, then continue Queue and background worker The process manager function that finishes the request sends the response, and the script keeps running The request saves a job, and a separate worker process runs it No extra infrastructure Needs a queue and worker processes Still occupies a web worker, with no retries or visibility Frees web workers, with retries, monitoring and scaling Best for small, quick follow-up tasks Best for emails, exports, partner calls and anything that can fail Designing the queued approach:
- Return 202 Accepted with a link to check the job's status, for long tasks such as reports.
- Save the job and the business change together, or use an outbox, so no job is lost or orphaned.
- Make jobs idempotent and retry with backoff.
- Notify the user when the work finishes, by email, a notification or status polling.
Q96.How do you make a PHP application observable in production?
Collect three kinds of signals: structured logs, metrics and distributed traces, all tagged with a request ID and release version. In the request-per-process model, each request is short-lived, so signals must be sent or written quickly without slowing the response.
What to collect:
- Request metrics: rate, error rate and latency per route, from the web server, the process manager or the application.
- Process manager health: active and idle workers, queue length and slow requests.
- Dependency metrics: database query time, cache hit rate and external API latency and errors.
- Traces: spans for the request, queries and outgoing calls, with trace context propagated to other services through headers.
- Logs: JSON records with the request ID, user or tenant identifier where allowed and error details.
- Background workers: job duration, failures, retries and queue age.
Implementation tips:
- Send telemetry to a local agent or collector over a fast local channel, so a slow backend does not slow requests.
- Use the process manager's slow log to capture stack traces of requests that exceed a threshold.
- Sample traces at high traffic, keeping all errors.
- Never include secrets or sensitive personal data in any signal.
Q97.What do you look for when reviewing PHP code?
Focus review time on security, correctness and maintainability issues that tools miss, and let formatters and static analysis catch style and type problems automatically.
Checklist:
- Types:
declare(strict_types=1);, typed parameters, returns and properties, and no loose comparisons. - SQL: prepared statements everywhere, no string-built queries and identifiers from allowlists.
- Output: escaping for HTML and other contexts, no raw user data in templates.
- Input: validation at the boundary, size limits and no trust in client headers.
- Authentication and authorization: checks on every endpoint, including ownership of the resource being accessed.
- Errors: exceptions instead of silent failures, no
@suppression and no sensitive details in responses or logs. - Resources and performance: no queries inside loops, streaming for large data and timeouts on external calls.
- State: no global variables or request data in static properties, especially in long-running workers.
- Dependencies: new packages justified and reviewed.
- Tests: behavior and failure paths covered.
Interview tip: naming one dangerous function you always flag, such as
unserializeon user input orextract, and explaining why, shows practical security awareness.- Types:
Q98.How does PHP compare with other common approaches for backend development?
PHP is strong for web applications and APIs with a simple, robust execution model and very easy hosting. Other approaches can be better fits for long-lived connections, heavy computation or teams with different experience.
PHP Compiled languages with lightweight concurrency Garbage-collected virtual machine languages Request-per-process model, with share-nothing isolation Single binaries handling many concurrent connections Long-running services with mature threading Very fast development and deployment for web applications High performance for network services and infrastructure tools Large enterprise ecosystems and tooling Long-running servers and asynchronous libraries exist, but are less common Smaller web ecosystems for full-stack features Heavier runtime and slower startup Best for content sites, e-commerce, APIs and admin applications Best for high-concurrency services and real-time systems Best for large, complex domain systems Questions that decide it:
- What does the team already know and operate well?
- Does the workload involve many long-lived connections or heavy computation?
- Which existing systems, libraries and hosting options must it fit?
Modern PHP with strict types, static analysis and a good architecture is a solid choice for most web backends; the deciding factor is usually the team and the workload, not the language's reputation.
Q99.How should you explain a PHP backend project you personally worked on?
Tell one project as a story: what the system did, your role, the key decisions, a hard problem you solved and the measured results. Interviewers want to see that you write modern, secure PHP and understand how it runs in production.
- Context: the application, its users and its scale, such as requests per minute or orders per day.
- Your role: what you personally designed or built.
- Decisions: how you structured the code, handled data access, secured the application, cached and used queues, and why.
- A hard problem: an N+1 query issue, a memory limit in a batch job, duplicated webhooks, a session problem behind load balancers or a legacy migration, with the evidence that led to the fix.
- Quality: tests, static analysis level and deployment process.
- Results: numbers such as "checkout errors fell by 80%" or "the export now runs in 4 minutes instead of 2 hours".
- Reflection: what you would change now.
What the interviewer is listening for:
- Modern practices: types, dependency injection, prepared statements and tests.
- Understanding of the execution model, process manager and caching.
- Pragmatic trade-offs and measurable impact.
Interview tip: if your experience includes legacy code, describe how you improved it step by step; interviewers value safe modernization highly.
Q100.How do you discuss a production incident you handled in a PHP application?
Describe the incident from detection to prevention, focusing on how you restored service first and then found the cause with evidence such as logs, slow request traces, process manager status and database metrics.
- Detection: the alert or customer report, and how quickly it arrived.
- Impact: what users experienced, for how long and how many were affected.
- Mitigation: rollback, disabling a feature, scaling workers or blocking abusive traffic, and why you chose it.
- Diagnosis: for example, the process manager showing all workers busy, slow logs pointing to one query and the database showing a missing index after a deployment.
- Root cause and contributing factors: such as a migration that dropped an index, or a missing timeout on an external call.
- Fix: the change and the test or check that proves it.
- Prevention: alerts on worker saturation, query reviews in deployment, timeouts by default and runbook updates.
What the interviewer is listening for:
- Clear priorities under pressure: users first, then root cause.
- Evidence-based investigation rather than guesses.
- Blameless, system-focused language.
- Lasting improvements, verified after the fix.