cswork / libraryOpen practice workspace

Interview guides · Bixal

Bixal Software Engineer Interview Guide

Bixal Software Engineer interview guide: the reported 3-round loop, Drupal and WordPress questions, PHP coding, Section 508 prep and a 7-day plan.

The 60-second brief

Bixal builds web applications for federal agencies, non-profit organizations and other public sector clients. Candidates describe the engineering work as centred on the content management systems Drupal and WordPress, connected to backend services and cloud hosting. A Software Engineer there writes custom modules, plugins and themes, migrates data in from older systems, integrates external APIs, and looks after site performance and security. Accessibility for public-facing sites, including Section 508 compliance, is part of the picture candidates describe, and engineers work in cross-functional agile teams with product managers, UX/UI designers, QA engineers, DevOps specialists and client stakeholders.

This guide is for engineers interviewing for the Software Engineer role at Bixal, especially those with PHP, Drupal or WordPress experience. It covers the three reported rounds, the CMS, coding, security, accessibility and behavioral questions candidates report, and a seven-day plan that turns them into practice sessions.

Candidates report three rounds over roughly 3-5 weeks: an Initial Screening, a Hiring Manager Interview and a Technical Team Interview, with the hands-on coding concentrated in the last one.

Focus areas

  • WordPress
  • Drupal
  • Problem Solving

Overview

The reported questions follow the shape of the job. Candidates describe day-to-day work on Drupal and WordPress installations: custom modules and plugins, themes built from design mockups, integrations with external databases and APIs, and ownership of site speed and security. So the technical questions are practical and stack-specific. You are asked how you would extend a CMS without touching core, how you would move legacy data into Drupal, how you would speed up slow queries and caches on a busy WordPress site, and how you would secure and test PHP code.

Two themes sit across the technical questions. The first is trade-offs you can defend: why a custom module instead of a contributed one, why a cache layer at one level rather than another, why a migration is staged the way it is. The second is public-sector delivery. Section 508, WCAG and the OWASP-style vulnerabilities (SQL injection, XSS) are named in the reported questions and in the evaluation areas candidates describe, so accessibility and security need concrete answers, not slogans.

The requirements candidates describe list PHP with object-oriented practice, custom modules, plugins and themes for both CMSs, relational databases and query optimisation, HTML5, CSS3 and JavaScript, and Git branching workflows. Nice-to-have items include federal client experience, React, Vue or Angular, Docker or Lando, and CI/CD with hosts such as Acquia, Pantheon or AWS. Candidates also describe a minimum of three to five years of professional development. Use that list as a checklist when you choose which projects to talk about.

Because the loop opens with two conversational rounds before the technical team interview, plan to prepare in that order: a clear career story, then project stories with measurable outcomes, then live PHP and JavaScript. Candidates report the final round emphasises coding fundamentals, and they also report scheduling delays between stages, so keep your recruiter informed and confirm each appointment the day before.

The Bixal interview process

Candidates describe these three stages in this order. The hiring manager and technical stages are where most of the reported questions are practised, and the coding stage is the one candidates single out as demanding.

Initial Screening

Candidates report this first stage as a recruiter conversation about your background, career goals and basic qualifications. It is where your experience gets matched to the role's listed requirements: PHP, Drupal and WordPress modules, plugins and themes, SQL, front-end basics and Git. Prepare a short, specific career narrative rather than a list of employers, and be ready to say which of the nice-to-have items (federal clients, Section 508, Docker or Lando, CI/CD, Acquia, Pantheon, AWS) you have really used.

What to demonstrate

  • How your background lines up with the PHP, CMS, SQL and front-end requirements
  • Whether your career goals fit a role centred on Drupal and WordPress delivery
  • Basic qualifications such as years of professional development experience

How to prepare

  • Write a one-paragraph career summary that names the CMS projects, PHP versions and hosting platforms you have worked with, and which you owned.
  • Map your experience against each must-have skill and mark any gap you would address honestly, such as Drupal depth if you are mostly WordPress.
  • Note any accessibility or public-sector work, since candidates report mentioning Section 508 and WCAG experience helps.
  • Ask the recruiter to confirm the later stages and their scheduling, and confirm each appointment a day ahead because candidates report scheduling delays.

Hiring Manager Interview

Candidates report a deeper conversation with the hiring manager about what the role expects, the projects you have delivered, and fit with Bixal's core values. Expect your past work to be examined in detail, so choose projects where you made technical decisions and can state the outcome. The behavioral questions candidates report (shifting client requirements mid-sprint, working with designers, resolving a technical disagreement, prioritising deadlines) belong in your preparation for a conversation like this one.

What to demonstrate

  • Depth and ownership in the past projects you describe
  • Understanding of what the Software Engineer role involves day to day
  • How you work with designers, product managers, QA and client stakeholders

How to prepare

  • Prepare two projects in STAR form, naming the technical choices you made (module versus plugin, caching layer, migration approach) and the measurable result.
  • Rehearse the four reported behavioral stories until each fits a short spoken answer with a clear decision and outcome.
  • Prepare a plain-language explanation of one technical trade-off for a non-technical stakeholder, since candidates report communication with such stakeholders matters.
  • Write questions about how the team runs sprints, code review and deployment, drawn from the responsibilities candidates describe.

Technical Team Interview

Candidates report this stage as a conversation with prospective engineering peers about technical requirements and problem-solving method, with hands-on coding. They also say coding fundamentals are weighted heavily here, with clean PHP and JavaScript written on the spot. The evaluation areas candidates describe are CMS architecture and development, coding and technical problem solving, and accessibility with security. Example scenarios include securely parsing an external XML feed into custom post types, refactoring legacy code that is slow or memory-hungry, and fixing accessibility failures in a custom interactive form.

What to demonstrate

  • Working PHP and JavaScript written live, including error handling and edge cases
  • Object-oriented design, API consumption and creation, and performance profiling
  • Knowledge of Drupal and WordPress extension points, database work and configuration across environments
  • Accessibility and security fundamentals: semantic HTML, WCAG, input sanitisation, output escaping

How to prepare

  • Write PHP aloud without an IDE: restructure a nested JSON payload, validate an XML feed, and handle malformed or missing keys with explicit errors.
  • Rehearse the reported CMS questions: Drupal module versus WordPress plugin, staged migrations with ID mapping, slow-query diagnosis, and Memcached, Redis or Varnish layering.
  • Prepare a concrete checklist for making a custom form accessible (labels, keyboard order, focus, error announcements, contrast) and for securing a search box (prepared statements, output escaping).
  • Practise explaining trade-offs between speed, security and maintainability out loud while you code, and have a unit and integration testing setup for PHP you can describe.

Where candidates lose points

Describing a Drupal module and a WordPress plugin in the same generic terms, without naming either system's extension points

Be ready to name the real mechanisms: hooks, services and event subscribers on the Drupal side, actions and filters in a plugin on the WordPress side, and the rule of never editing core. Say why each approach keeps upgrades safe.

Answering the legacy migration question with 'I would write an import script'

Describe the stages: inventory the source, map fields and IDs, preserve taxonomy relationships and metadata, run repeatable imports on staging, validate counts and samples, and keep a rollback plan. Mention custom Drush commands only if you have used them.

Saying 'add a cache' for slow pages or queries before measuring anything

Start with the slow query log or a profiler, read the query plan, add or fix indexes, then explain which layer caches what (object cache with Redis or Memcached, page cache with Varnish) and how it is invalidated.

Citing Section 508, WCAG and 'sanitizing input' as buzzwords with no concrete checks

Give checkable specifics: semantic landmarks, labels and error text tied to fields, keyboard order and focus, contrast, prepared statements for SQL, and escaping on output for XSS. Practise applying them to a custom form and a search interface.

Behavioral stories told as 'we' with no personal decision, outcome or lesson

For each story, state the decision you owned, the alternative you rejected, the result and what you would do differently. Prepare the four reported scenarios so none is told for the first time in the interview.

Questions & practice

How do you ensure your code complies with Section 508 accessibility standards and WCAG guidelines?

Medium · Core Coding & Problem-Solving

Approach

  1. Name the target: the refreshed Section 508 standards incorporate WCAG 2.0 Level A and AA by reference, and many teams build to WCAG 2.1 AA, which adds criteria such as reflow at 320 CSS pixels and non-text contrast.
  2. Build it in: semantic HTML first (button rather than a clickable div, a label for every input, headings in order, landmark elements), ARIA only when no native element fits, full keyboard operability with a visible focus indicator, focus trapped and returned correctly in modals, text contrast of at least 4.5:1 (3:1 for large text), meaningful alt text, and no information conveyed by color alone.
  3. Forms deserve their own pass: errors tied to fields with aria-describedby, error summaries announced through a live region, required fields marked in text and not only by an asterisk color.
  4. In a CMS the theme is only half of it: templates must output accessible markup, and the editor must not let content break it, for example a required alt text field, restricted heading levels in the WYSIWYG configuration, and guidance on link text.
  5. Test in layers: automated checks (axe-core, Pa11y or Lighthouse) in CI catch only part of the failures, so add a manual keyboard-only pass, screen reader checks (NVDA or JAWS on Windows, VoiceOver on macOS and iOS), and 200% zoom. Federal buyers often ask for an Accessibility Conformance Report based on the VPAT template, so I keep the findings documented.

Follow-up

  • How would you make a custom autocomplete dropdown accessible to keyboard and screen reader users?
  • The brand's primary color fails contrast on white buttons. What do you do?
  • Editors keep uploading scanned PDFs. How do you handle accessibility for that content?

Canonicalise a request body into a stable idempotency fingerprint

Medium · parsing · canonicalisation · hashing

idempotency_key.request_fingerprint is a SHA-256 over the method, path and canonicalised body, and a retry whose fingerprint differs must be rejected with 422 rather than served the stored response. Write the canonicaliser. Bodies are JSON up to 256 KB nested at most 32 levels; clients vary key order, whitespace and unicode escaping, and some send 64-bit ids as JSON numbers. Produce a deterministic byte string such that semantically identical bodies match and any semantic difference does not. State your complexity and name two normalisations you refuse to perform.

Approach

  1. Parse once into a tree, then re-serialise under fixed rules: object keys sorted, array order preserved, one escaping convention, no insignificant whitespace. Parsing is O(n) and sorting keys is O(k log k) per object, so O(n log n) overall with O(depth) stack, and the 32-level cap is enforced during parsing because hostile nesting is how a canonicaliser becomes a stack overflow.
  2. Sort keys by their UTF-8 bytes and say why the obvious implementation is wrong in some runtimes: a default string comparison that orders by UTF-16 code units places surrogate pairs, meaning code points from U+10000 up, below U+E000 to U+FFFF, which is not UTF-8 byte order, so two services written in different languages disagree on the same document.
  3. Do not re-encode numbers through a double. IEEE-754 binary64 represents integers exactly only up to 2^53, so normalising a 19-digit id through a float changes it, and 1 against 1.0 cannot be reconciled without deciding whether they are the same value. Preserve the literal token, and require ids as strings at the API boundary if you want them comparable.
  4. Reject duplicate keys rather than picking one. JSON permits them and parsers disagree, most keeping the last, so any choice you make ties the fingerprint to a parser detail that the code handling the request does not necessarily share.

Follow-up

  • A client sends the same logical request with an extra field your API ignores. Same key, different fingerprint, so you return 422. Is that the right answer?
  • Where does the fingerprint get computed relative to request decompression and the body-size limit?

Find overlapping job attempts and peak concurrency from lease records

Medium · sweep line · intervals · leases

A day of job_run history yields about 50,000,000 attempt records: (job_run_id, job_type, attempt, started_at, finished_at which is NULL when the worker died, lease_expires_at). Leases expire on a clock, so a job that outran its lease ran twice. Produce (a) every job_run_id whose attempts overlapped in wall-clock time and (b) the peak number of simultaneously running attempts per job_type with the minute it occurred. Target O(n log n). State how you treat a NULL finished_at and what clock skew does to your answer.

Approach

  1. Define the interval before sorting anything: an attempt occupies [started_at, COALESCE(finished_at, lease_expires_at)). finished_at is observed and lease_expires_at is only a promise, so every attempt without a finish contributes an estimate and the whole result is a lower bound on overlap rather than an exact count.
  2. For peak concurrency, sweep: emit 2n endpoints, sort by (timestamp, kind) with ends ordered before starts at equal timestamps, then walk the sequence maintaining a counter per job_type and record each type's maximum with its timestamp. O(n log n) dominated by the sort, O(n) space, or O(1) extra if the sort is external and the walk streams.
  3. For overlap detection, do not compare attempts pairwise. A single global sort by (job_run_id, started_at) gives both the grouping and the order; within a group, keep the maximum end seen so far and report an overlap exactly when the next start is less than that running maximum, which is one linear pass after the sort.
  4. Half-open intervals matter and are easy to get wrong: with closed intervals an attempt ending at the same millisecond another begins reads as concurrency two, and across 50,000,000 records that artefact swamps the real signal.

Follow-up

  • A handler is not idempotent and you have found 400 overlapping jobs. Which of them actually caused damage, and what would you query to find out?
  • Peak concurrency for one job_type is 4 against a configured cap of 4. Is the cap working, or is the data hiding attempts that never started?

Describe your approach to securing web applications against common vulnerabilities such as SQL injection and C

Medium · Core Coding & Problem-Solving

Describe your approach to securing web applications against common vulnerabilities such as SQL injection and Cross-Site Scripting (XSS).

Approach

  1. SQL injection: never concatenate input into SQL. Use prepared statements with bound parameters: PDO prepare/execute, $wpdb->prepare with %s, %d and %f (and %i for identifiers since WordPress 6.2), or Drupal's database API with named arguments such as :nid. Run $wpdb->esc_like on LIKE search terms before preparing.
  2. Identifiers and keywords (a sort column, ASC or DESC) cannot be bound as values, so map user input against an allowlist. Connect with a least-privilege database account so a successful injection reaches less.
  3. XSS: escape on output for the specific context: esc_html for text, esc_attr for attributes, esc_url for links, wp_json_encode or esc_js for script data. Drupal's Twig auto-escapes, so the risk is |raw and markup built in PHP; use Html::escape or Xss::filter there. In JavaScript, set textContent rather than innerHTML. This covers stored, reflected and DOM-based XSS.
  4. Sanitizing input (sanitize_text_field, type casting) keeps data clean but does not replace output escaping, because the same value can land in HTML, an attribute or a URL. Rich text from editors goes through an allowlist filter (wp_kses_post, or Drupal text formats with the HTML filter).
  5. Defence in depth and verification: a Content-Security-Policy header that blocks inline script, HttpOnly, Secure and SameSite cookies, CSRF protection (WordPress nonces, Drupal form tokens) with capability checks, PHPCS with the WordPress Coding Standards security sniffs, a scanner such as OWASP ZAP, and prompt updates for vulnerable plugins and modules.

Follow-up

  • The page lets users choose the sort column and direction. How do you build that query safely?
  • Why isn't sanitizing every input on the way in enough to stop XSS?
  • How would you roll out a Content-Security-Policy on a site full of inline scripts from third-party plugins?

Write the update path that detects a concurrent edit

Medium · optimistic concurrency · transactional outbox · audit history

resource carries version INT NOT NULL DEFAULT 1. resource_revision holds revision_id, resource_id, version, actor_user_id, change_kind, patch JSONB, request_id, created_at with UNIQUE (resource_id, version). outbox_event holds aggregate_type, aggregate_id, aggregate_version, event_type, payload, status. A PUT carries the version the client read. Write the exact statements for the single transaction that applies the edit, records the revision and enqueues 'resource.updated', and give the handler's branch on zero affected rows. Then say what PostgreSQL 16 does under READ COMMITTED when two of these updates hit one row at once.

Approach

  1. One transaction, three writes, no network call inside it: UPDATE resource SET title = $3, version = version + 1, updated_at = now() WHERE resource_id = $1 AND tenant_id = $4 AND version = $2; then INSERT the resource_revision row at version $2 + 1; then INSERT the outbox_event row at the same aggregate_version. The event goes to a table rather than a broker because no transaction spans both.
  2. Branch on the affected-row count before doing anything else. Zero has three causes — stale version, wrong tenant, row gone — so re-read once and map to 409 carrying the current version, or 404 for an id outside the caller's tenant, which also stops the endpoint confirming that another tenant's id exists.
  3. State the engine behaviour instead of assuming it. Under READ COMMITTED the second UPDATE blocks on the row lock, and when the first commits PostgreSQL re-evaluates the WHERE clause against the newly committed row, so the version predicate now fails and the statement reports zero rows. Under REPEATABLE READ the identical collision raises SQLSTATE 40001 instead, so the handler must fold both shapes into one conflict response.
  4. Keep UNIQUE (resource_id, version) even though the predicate already serialises writers. It is what makes a lost update unwritable if any other path ever reaches the revision table, and it converts a logic bug into 23505 rather than into a silently missing history row.

Follow-up

  • A client sends the version it read ten minutes ago and the resource has moved three versions. What is in your 409 so it can resolve the conflict without a full re-fetch?
  • Two editors, two disjoint fields, no overlap. Does your answer still refuse the second write, and should it?

How do you approach building a custom module in Drupal versus a custom plugin in WordPress?

Medium · CMS & Backend Engineering

Approach

  1. Drupal side: a module is a folder with my_module.info.yml (name, type: module, core_version_requirement), my_module.module for hook implementations, my_module.routing.yml mapping paths to controller classes under src/ (PSR-4 namespace Drupal\my_module), and my_module.services.yml registering classes in the Symfony service container so dependencies are injected rather than fetched globally.
  2. WordPress side: a plugin is a folder whose main PHP file carries the Plugin Name header; it extends core through add_action and add_filter, registers custom post types on init, REST routes on rest_api_init with a permission_callback, and uses register_activation_hook (dbDelta for custom tables, default options). There is no built-in container, so I bring structure myself: namespaced classes and Composer autoloading.
  3. Extend, never edit core, in both. Drupal gives more scaffolding (entities, fields, plugin discovery via annotations or PHP attributes in newer versions, config schema, cache tags and contexts on render arrays); WordPress is lighter and faster to ship, but conventions and security discipline rest on the developer.
  4. Security per platform: Drupal permissions in my_module.permissions.yml enforced through a route permission requirement, Form API with built-in CSRF tokens, Twig auto-escaping; WordPress nonces (wp_nonce_field, check_admin_referer), current_user_can, sanitize* on input and esc_* on output.
  5. Lifecycle edge cases: schema or data changes on existing sites (Drupal hook_update_N or hook_post_update_NAME; WordPress a stored version option compared on load), cleanup on removal (hook_uninstall, uninstall.php), and multisite behaviour in WordPress.

Follow-up

  • A block shows different content per logged-in user. How do you keep Drupal's page caching from serving one user's version to another?
  • How would you make a setting differ between local, staging and production in each CMS?
  • How do you ship a database schema change to sites that already have your module or plugin installed?

Walk me through your process for managing complex database migrations from legacy systems into a modern Drupal

Medium · CMS & Backend Engineering

Walk me through your process for managing complex database migrations from legacy systems into a modern Drupal architecture.

Approach

  1. Use the Migrate API rather than one-off scripts: a source plugin (an SqlBase subclass with query(), fields() and getIds(), or a CSV source), a process pipeline, and a destination such as entity:node, entity:taxonomy_term, entity:user or entity:file. With migrate_plus, migrations live as YAML config and can be grouped.
  2. Order by dependency: users, files and taxonomy terms migrate before nodes, declared in migration_dependencies. migration_lookup translates a legacy ID into the new entity ID through the migrate_map tables, which is how taxonomy references and authorship survive; stubs cover forward and circular references.
  3. Process plugins do the transformation: static_map for legacy codes, default_value, sub_process for multi-value fields, callback for small cleanups, and a custom process plugin for legacy HTML that has to be cleaned before it goes into a text format.
  4. Make runs repeatable: the map table records source-to-destination IDs, so drush migrate:import can rerun with --update, migrate:rollback undoes a run, --limit tests a sample, and a high_water_property or track_changes picks up only rows that changed. Failed rows land in the message table instead of stopping the run.
  5. Validate before cutover: compare row counts per type between source and destination, spot-check sampled records field by field, migrate files and URL aliases with redirects from the old paths, rehearse on a copy of production data, then run a final delta import during a short content freeze.

Follow-up

  • Legacy articles reference each other in a cycle. How do you migrate them without losing links?
  • Editors keep publishing in the old system for two weeks after your first full import. How do you bring those changes across?
  • A 300,000-row migration runs out of memory midway. What do you change?

How do you optimize slow-loading database queries in a high-traffic WordPress environment?

Medium · CMS & Backend Engineering

Approach

  1. Measure before changing anything: Query Monitor on a staging copy to see each query, its time and the hook or plugin that issued it, the MySQL slow query log with a low long_query_time, and APM traces in production. Then run EXPLAIN on the worst offenders to check for full scans, filesort and temporary tables.
  2. Know the usual culprits: meta_query and orderby on wp_postmeta (meta_value is LONGTEXT and unindexed; meta_key is indexed only on its first 191 characters), LIKE '%term%' searches, ORDER BY RAND(), posts_per_page set to -1, and queries issued inside loops (the N+1 pattern).
  3. Cheap WP_Query fixes: 'no_found_rows' => true when there is no pagination (skips the row-count pass), 'fields' => 'ids' when only IDs are needed, and update_post_meta_cache / update_post_term_cache set to false when that data is not used.
  4. Structural fixes: move data that is filtered on into a taxonomy or a custom table with real indexes, add a prefix index where a meta lookup is hot, and check wp_options for autoload bloat (SUM(LENGTH(option_value)) WHERE autoload = 'yes') and piles of expired transients, which accumulate there when there is no persistent object cache.
  5. Then cache and scale reads: a persistent object cache (Redis or Memcached drop-in) so repeated queries do not hit MySQL, transients for expensive computed results, full-page caching for anonymous traffic, and read replicas for heavy read load. Confirm the gain by comparing query count and time before and after.

Follow-up

  • Users need to filter 500,000 posts on several custom fields at once. What do you build instead of a meta_query?
  • Why can't you put a full index on meta_value, and what do you do instead?
  • How do you find which plugin is loading megabytes of autoloaded options on every request?

Explain how you leverage caching mechanisms (such as Memcached, Redis, or Varnish) to improve site performance

Medium · CMS & Backend Engineering

Explain how you leverage caching mechanisms (such as Memcached, Redis, or Varnish) to improve site performance.

Approach

  1. Name the layers and what each stores: OPcache keeps compiled PHP bytecode; an object cache (Redis or Memcached, through the Drupal redis module or a WordPress object-cache.php drop-in) holds query results and computed data; Varnish or a CDN holds whole HTTP responses; browser caching follows Cache-Control headers.
  2. Full-page caching serves anonymous traffic: Varnish bypasses requests that carry session cookies, so I strip irrelevant cookies (analytics) in VCL or the hit rate collapses, and I never cache a response that sets a cookie or contains user-specific data.
  3. Invalidation is the hard part. In Drupal, cache tags (node:42, node_list) and cache contexts (user roles, language, URL) on render arrays let the Purge module invalidate Varnish by tag when content is saved. In WordPress I purge the affected URLs from a save_post hook, and fall back to short TTLs where purging is impractical.
  4. Redis or Memcached: Memcached is a simple multithreaded in-memory key-value store with no persistence; Redis adds data structures, optional persistence, replication and configurable eviction (allkeys-lru, for example), and can also hold sessions or queues.
  5. Edge cases: cache stampede when a hot key expires (a lock around regeneration, or serving stale content, as Varnish grace mode does), WordPress nonces outliving their default 24-hour validity inside long-cached pages, and measuring the outcome through hit ratios (varnishstat, Redis INFO keyspace_hits and keyspace_misses) and response times.

Follow-up

  • Logged-in editors bypass Varnish entirely. How do you keep their pages fast?
  • An editor says an update is not showing on the live site. Which response headers and logs do you check first?
  • What happens to the site when Redis fills up or goes down, and how should the application behave?

Write a PHP function to parse a nested JSON payload and restructure it for a specific API endpoint.

Medium · Core Coding & Problem-Solving

Approach

  1. Decode defensively: json_decode($json, true, 512, JSON_THROW_ON_ERROR) inside try/catch for JsonException (PHP 7.3+). Without that flag json_decode returns null on failure, which cannot be told apart from a valid JSON null without checking json_last_error().
  2. Validate the shape before mapping: required keys with array_key_exists (isset returns false for keys whose value is null), type checks on each field, and ?? defaults only for keys that are truly optional. Return or throw a clear error naming the missing path rather than letting a notice surface later.
  3. Restructure with an explicit mapping function per target field, plus a recursive helper for arbitrary depth (for example, flattening to dot-notation keys by passing a prefix down). array_is_list (PHP 8.1+) separates JSON arrays from objects, and array_map handles lists of items. Each node is visited once, so the cost is O(n) in total nodes, with recursion depth bounded by the nesting.
  4. Encode for the endpoint: json_encode with JSON_THROW_ON_ERROR, plus JSON_UNESCAPED_SLASHES and JSON_UNESCAPED_UNICODE if the consumer expects them. An empty PHP array encodes as [], so cast to (object) [] where the API requires {}; large integer IDs may need JSON_BIGINT_AS_STRING on decode to avoid float precision loss.
  5. Test the edges with PHPUnit data providers: missing keys, explicit nulls, empty arrays, unexpected types, Unicode, and nesting deeper than the depth limit. Send the result with a timeout and check the response status (wp_remote_post or Guzzle).

Follow-up

  • The payload grows to several hundred megabytes. What changes in your approach?
  • The endpoint rejects your request because an empty field arrived as [] instead of {}. How do you fix it without special-casing every field?
  • How would you make the mapping configurable so a second endpoint with a different shape does not need a new function?

How do you implement automated testing, such as unit testing or integration testing, within a PHP-based workfl

Medium · Core Coding & Problem-Solving

How do you implement automated testing, such as unit testing or integration testing, within a PHP-based workflow?

Approach

  1. Split by layer: PHPUnit unit tests for pure logic (mappers, validators, price or date rules) using test doubles from createMock or Mockery; integration tests against a real database; a few end-to-end browser tests (Behat, Playwright or Cypress) for critical journeys such as forms and login.
  2. WordPress: wp scaffold plugin-tests (WP-CLI) generates a bootstrap that loads the core test suite, so tests extend WP_UnitTestCase, run against a separate test database and build fixtures with the factory ($this->factory->post->create()). Brain Monkey or WP_Mock cover unit tests that should not load WordPress at all.
  3. Drupal: PHPUnit test types match the layers: UnitTestCase for isolated classes, KernelTestBase for a minimal bootstrap with the database and selected modules, BrowserTestBase for functional tests through a simulated browser, and WebDriverTestBase when JavaScript must run.
  4. Design for testability: keep hook callbacks thin and delegate to classes whose dependencies are injected (Drupal services, constructor injection in plugins), so the logic is testable without the CMS. Use data providers for edge cases instead of copy-pasted tests.
  5. Wire it into CI on every pull request: composer install, PHPCS, static analysis (PHPStan or Psalm), PHPUnit with coverage, a database service container, and a PHP version matrix. Keep tests deterministic by mocking HTTP and the clock; flaky tests get fixed or quarantined, not ignored.

Follow-up

  • How do you test code that calls an external API without hitting it in CI?
  • You inherit a plugin with no tests and a release next week. Where do you start?
  • What coverage or quality bar would you put on merges, and why that one?

Explain your experience with RESTful API design and how you handle authentication and rate limiting.

Medium · Core Coding & Problem-Solving

Approach

  1. Resource design: nouns for resources, HTTP methods used for their semantics (GET safe, PUT and DELETE idempotent, POST not, PATCH for partial updates), accurate status codes (201 with Location, 400 or 422 for invalid input, 401 versus 403, 404, 409 for conflicts, 429 for rate limits), a consistent error body such as RFC 9457 problem details, cursor or offset pagination, and an explicit versioning scheme.
  2. Authentication by client type: session cookie plus CSRF token for same-origin browser calls; API keys or OAuth 2.0 client credentials for server-to-server; authorization code with PKCE for user-delegated third-party access. With JWT bearer tokens, keep expiry short and verify signature, algorithm, exp and aud on every request.
  3. In the CMSs: WordPress REST routes need a permission_callback (WordPress 5.5+ flags routes registered without one; __return_true makes a route deliberately public), and auth is cookie plus the wp_rest nonce sent as X-WP-Nonce, or application passwords (5.6+) over HTTPS. Drupal offers cookie and basic_auth in core and OAuth through the simple_oauth contrib module.
  4. Rate limiting: a token bucket or sliding window keyed per API key, user or IP, with counters in a shared store such as Redis (an atomic INCR plus EXPIRE, or a Lua script) so every app server agrees. Return 429 with a Retry-After header, and enforce coarse limits at the gateway, CDN or nginx limit_req before requests reach PHP.
  5. Edge cases: idempotency keys so a retried POST does not create duplicates, a CORS allowlist rather than a wildcard on authenticated endpoints, no credentials in query strings (they end up in logs), and key rotation that lets two keys be valid during the overlap.

Follow-up

  • A fixed-window limiter of 100 requests per minute lets a client send nearly 200 within a few seconds. Why, and what do you use instead?
  • A partner's API key has leaked. How do you rotate it without breaking their integration?
  • How do you introduce a breaking change to an endpoint that external clients already depend on?

p99 jumped on one listing filter while p50 stayed flat

Medium · query planning · composite index · percentiles

After a release that added an owner_user_id filter to the resource listing, p99 rose from 90 ms to 1.9 s while p50 stayed at 40 ms. Traffic and row counts are unchanged. resource carries the index (tenant_id, status, updated_at DESC, resource_id DESC). The new query filters tenant_id and owner_user_id, orders by updated_at DESC, resource_id DESC, and takes 20 rows. On PostgreSQL, explain the shape of the regression, prove it from a query plan, and give the index you would add.

Approach

  1. Start from the shape. A flat p50 with a moved p99 means a subset of requests changed cost, not all of them, so the first job is naming the subset. Bucket the endpoint's latency by the tenant's row count; the natural hypothesis is that large tenants are a small share of requests and all of the tail.
  2. Get the plan for the new query on a large tenant with EXPLAIN (ANALYZE, BUFFERS). Expect an index scan over the tenant's range, a filter discarding most of it, then a Sort feeding the Limit, possibly reporting Sort Method: external merge Disk. Read actual rows on the scan node, not estimated.
  3. Explain why the existing index cannot serve it. A composite B-tree is seekable only as a left prefix, and with no equality predicate on status the scan cannot treat updated_at as an ordering, because rows in the tenant's range are ordered by status first. Everything matching must be read and sorted before LIMIT 20 can apply, so a tenant with 400,000 rows pays 400,000 rows to return 20.
  4. Add (tenant_id, owner_user_id, updated_at DESC, resource_id DESC). Equality on the first two columns leaves the index ordered by updated_at within that pair, so the plan becomes an index scan that stops after 20 rows with no Sort node. PostgreSQL can scan a B-tree backwards, so the DESC markers matter only if the two sort columns ever disagree in direction; keeping them explicit documents the order the keyset cursor depends on.

Follow-up

  • The endpoint paginates with OFFSET. What does page 500 cost with your index, and what does the keyset version cost?
  • How would you have caught this before release, given that a 10,000-row seed database produces the same plan shape at an unnoticeable cost?

Suggested practice checklist

The plan moves from mapping the three reported rounds to the Drupal and WordPress, performance, PHP and API, accessibility and security, and behavioral questions candidates report, and ends with a full mock.

Day 1 — Map the loop and your CMS experience

  • Write the reported sequence (Initial Screening, Hiring Manager Interview, Technical Team Interview) and list what each stage covers.
  • Match your experience to the must-have skills (PHP and OOP, Drupal and WordPress modules, plugins and themes, SQL, front end, Git) and mark your weakest.
  • Write your career summary in a form you can say aloud, with two projects named.

Deliverable: A one-page loop map, a skills-versus-requirements table with gaps marked, and a written career summary.

Day 2 — Extension points and migrations

  • Answer the module versus plugin question aloud, naming the extension points on each side and how each avoids touching core.
  • Outline a migration of legacy articles into Drupal: source inventory, field and ID mapping, taxonomy and metadata, staged runs, validation, rollback.
  • Note how configuration moves between local, staging and production.

Deliverable: A spoken answer on module versus plugin, and a written migration outline with its validation checks.

Day 3 — Performance, queries and caching

  • Write a diagnosis sequence for slow queries on a busy WordPress site: slow query log, query plan, indexes, query count per request, transients and options.
  • Draw the cache layers (object cache with Redis or Memcached, page cache with Varnish) with what each stores and how it is invalidated.
  • Work through the practice debugging scenario about a latency spike on one filter while the median stayed flat.

Deliverable: A diagnosis checklist, a cache layer diagram with invalidation rules, and notes from the practice scenario.

Day 4 — PHP coding and APIs

  • Write the PHP function that parses a nested JSON payload and reshapes it for a target endpoint, with checks for missing keys and bad types.
  • Sketch a REST design covering resource naming, authentication, and rate limiting, and the response when a limit is hit.
  • Describe how you would set up unit and integration tests for PHP code, then try the practice coding exercise on canonical request bodies.

Deliverable: A working PHP function with test cases, a one-page REST design with auth and rate limit rules, and a testing setup outline.

Day 5 — Accessibility and security

  • Build a checklist for making a custom form meet WCAG 2.1 AA: labels, keyboard order, focus, error messages, contrast.
  • Write out how to prevent SQL injection and XSS in a custom search interface, separating input validation, parameter binding and output escaping.
  • Describe where Section 508 checks fit in a team workflow (design review, code review, automated and manual testing).

Deliverable: An accessibility checklist for forms and a security walkthrough for a search feature, each spoken aloud once.

Day 6 — Behavioral stories

  • Write STAR outlines for the production white-screen incident and the mid-sprint requirements change, with your own decisions and measurable results.
  • Write outlines for the designer collaboration story and the technical disagreement, including how it was resolved.
  • Prepare a prioritisation answer built on a real example with overlapping deadlines.

Deliverable: Five STAR outlines, each rehearsed aloud once, with the decision, result and lesson stated.

Day 7 — Full mock of the technical stage

  • Run a mock technical round: one CMS design question, one live PHP exercise and one security or accessibility question, answered aloud.
  • Do the practice exercise on overlapping job attempts in your strongest language to keep general coding sharp.
  • Review your gaps, prepare questions for the team, and write the recruiter message confirming your next appointment.

Deliverable: A scored self-review of the mock, a list of remaining gaps, and a list of questions for the team.

Behavioural preparation

The behavioral prompts candidates report for Bixal cover a production incident, client requirements that change mid-sprint, working with UX/UI designers on CMS themes, and a technical disagreement. They match the cross-functional agile setting candidates describe. Prepare each story with your own decision and a result you can state, and have one example of explaining a technical trade-off to someone non-technical.

  1. Tell me about a time you explained a technical trade-off to a non-technical stakeholder.

Describe a time you had to debug a critical WSOD (White Screen of Death) in a production environment. What ste

Medium · CMS & Backend Engineering

Describe a time you had to debug a critical WSOD (White Screen of Death) in a production environment. What steps did you take?

Approach

  1. Choose an incident where you led the diagnosis. Open with the symptom and the stakes in two sentences: a blank page on a production site, which pages, and who noticed.
  2. Show an evidence-first sequence. A white screen in PHP usually hides a fatal error, so I read the PHP and web server error logs first (and the Drupal database log or Drush watchdog output, or the WordPress debug log written to a file, never displayed to visitors).
  3. Narrow the cause with a diff, not guesses: what shipped last (plugin or module update, config import, theme change, memory or PHP version change), then reproduce on staging and confirm by disabling the suspect component, for example by renaming its folder or using a command-line tool.
  4. State the call you made between rolling back and fixing forward, and why. Then close with the root cause, the fix, and the safeguard you added, such as staging smoke tests, a deploy check or error alerting.
  5. The mistake that sinks this story is a flurry of changes on production with no log reading, or a result that is just 'it came back'. Give the time to recovery only if you actually measured it.

Follow-up

  • What would you have done if the logs were empty or error display was disabled?
  • Why did you roll back instead of patching forward, or the other way round?
  • What did you change afterwards so the same class of failure is caught before production?

Tell me about a time when client requirements changed significantly mid-sprint. How did you adapt your technic

Medium · Behavioral & Collaboration

Tell me about a time when client requirements changed significantly mid-sprint. How did you adapt your technical approach?

Approach

  1. Pick a real change, such as new content types, a different data source or a revised workflow, that landed after sprint planning. Say what was committed, what changed, and who asked for it.
  2. Show impact analysis before reaction: what existing work was affected, what could be reused, and what the change touched in the CMS (content model, migrations, templates, integrations).
  3. Describe how you reached an agreement on scope with the product manager and client: swapping items rather than silently adding work, splitting the request into a minimal version now and the rest later, and writing down the decision.
  4. Name the technical adaptation you made, for example keeping the change behind configuration, adjusting a migration mapping, or isolating the changed piece in its own module, so later changes cost less.
  5. Do not make the story about a difficult client or about heroics. A strong answer ends with what shipped, what moved out of the sprint, and how you changed estimation or review practice afterwards.

Follow-up

  • How did you decide what to push out of the sprint to make room?
  • How did you protect testing and accessibility checks when the schedule got tighter?
  • What would you set up so a similar change is cheaper next time?

How do you collaborate with UX/UI designers when translating complex, highly interactive designs into function

Medium · Behavioral & Collaboration

How do you collaborate with UX/UI designers when translating complex, highly interactive designs into functional CMS themes?

Approach

  1. Frame it as a working process, not a handoff: I review the designs before build starts and flag what is expensive or inaccessible, such as custom widgets without a keyboard model or a layout that breaks at narrow widths.
  2. Agree shared vocabulary early: a component inventory, spacing and colour tokens, breakpoints and states (hover, focus, error, disabled), so the design maps cleanly to theme templates and reusable components.
  3. Cover interactivity and accessibility together with the designer: focus styles, contrast, keyboard behaviour, error messages for forms, and semantic HTML so screen readers get the same structure sighted users see.
  4. Review the built result side by side with the design in the browser and on real devices, and keep a loop for adjustments instead of silently deviating when something is hard to build.
  5. Use one concrete example in STAR form: a design that was hard to translate, the compromise you reached, and how the editors using the CMS (not just visitors) were considered.

Follow-up

  • What do you do when a design cannot meet WCAG as drawn?
  • How do you handle content editors entering content the design did not anticipate?
  • How do you keep the theme maintainable when designs change after build?

Describe a situation where you had a technical disagreement with a teammate. How did you resolve it and reach

Medium · Behavioral & Collaboration

Describe a situation where you had a technical disagreement with a teammate. How did you resolve it and reach a consensus?

Approach

  1. Choose a disagreement about substance, such as contributed module versus custom code, where to cache, whether to refactor now or later, or how to structure a migration, not a clash of personalities.
  2. Lay out both positions fairly, including the strongest argument for the other side, so it is clear you understood it.
  3. Describe how you moved from opinion to evidence: a small prototype, a measurement of query time or memory, a look at the Drupal or WordPress documentation, or a review of the maintenance cost, and agreed decision criteria before comparing options.
  4. State the outcome honestly, including when your own position lost, and how the team recorded the decision, for example in the pull request or a short design note.
  5. The failure mode is a story where you were simply right and the colleague gave in. Show the working relationship afterwards and what you took from the exchange.

Follow-up

  • What if the evidence had been inconclusive, who would have made the call?
  • Have you ever changed your mind in a code review? What convinced you?
  • How do you give review feedback on code you would have written differently?

How do you prioritize your tasks when juggling multiple deadlines on a fast-paced project?

Medium · Behavioral & Collaboration

Approach

  1. Describe a method, not a mood: list everything with its deadline, who depends on it, and the cost of being late, then rank by impact and dependency rather than by who is asking loudest.
  2. Show that you surface the conflict early. Take the ranked list to the product manager or client and ask them to confirm the order, naming what slips if everything stays due.
  3. Mention the engineering side: protect time for code review, testing and accessibility checks, break large tasks into shippable slices, and keep blockers (waiting on design, QA or deployment) visible on the board.
  4. Use a real example with two or three overlapping items, such as a bug fix, a feature and a deployment, and say what you delayed on purpose and what happened as a result.
  5. A weak answer is 'I work longer hours' or 'I do the urgent thing first' with no trade-off conversation. Say how you re-plan when a new priority arrives.

Follow-up

  • What do you do when two stakeholders each say their item is the top priority?
  • Which task do you refuse to cut when time runs short, and why?
  • How do you tell a team member that their request is going to wait?