cswork / libraryOpen practice workspace

Interview guides · AlayaCare

AlayaCare Software Engineer Interview Questions & Guide 2026

API design practice for AlayaCare software engineer candidates: compatibility, versioning, and changes you cannot take back.

The 60-second brief

A Software Engineer at AlayaCare occupies a pivotal role in building the technology that powers the future of home-based care. By working on the company’s comprehensive platform, you are not just writing code; you are directly impacting the efficiency of healthcare providers and the quality of life for patients globally. This role demands a blend of technical precision and a deep sense of responsibility toward the end-user.

Ask whether any round happens inside an existing repository instead of a blank file. Reading unfamiliar code, isolating a fault and making the smallest correct change is a different skill from writing a function from scratch, and it needs its own practice.

Focus areas

  • Authorise every identified read against current consent
  • Make HL7 and X12 ingestion idempotent under replay
  • Model bitemporal coverage and retroactive eligibility changes

Overview

You will typically contribute to complex, full-stack environments, often utilizing Python for backend services and React for frontend interfaces. Whether working on clinical care enablement or supporting existing platform infrastructure, the work is characterized by high stakes and the need for scalable, reliable solutions. Success in this role requires a candidate who is as comfortable diving into deep technical architectural problems as they are collaborating with Product Managers and QA Engineers to ensure seamless delivery.

AlayaCare values candidates who demonstrate a genuine interest in the intersection of technology and healthcare. Highlighting your ability to build user-centric solutions will set you apart.

The AlayaCare interview process

AlayaCare candidates report 5 rounds · ≈ 4-6 weeks. The stages below are what candidates describe, not a published process.

Recruiter Screen

Half of this call is the part candidates treat as small talk: start date, notice period, work authorisation and its timing, location and time zone, on-call, and the number. Those are what kill offers late, after several engineers have each spent a day. Surfacing a hard constraint now costs you nothing and occasionally buys you something, since a loop compressed to fit a competing deadline can usually only be arranged if it is asked for early. The common failure is deflecting the compensation question twice, then discovering at offer stage that the band never reached your number.

What to demonstrate

  • Whether your hard constraints are compatible with the role before a loop gets booked: earliest start, notice period, what authorisation you hold and when it needs action, days on site, willingness to carry a pager
  • Whether you give a compensation range with something behind it, such as current total compensation or a competing timeline, rather than leaving the band untested
  • Whether your stated timeline is real, since a competing deadline raised now is something scheduling can sometimes work around and the same deadline raised at offer stage usually is not

How to prepare

  • Write each constraint down in one line before the call and state them as facts rather than negotiating them live under a question you were not expecting
  • Set your range from two or three current data points for that level and location, and name the structure you are quoting in, so the number is comparable to the one they are holding
  • If another process is running, say where it stands and by when, and ask directly whether this loop can be scheduled inside that window

Engineering Manager Interview

Part of what is being decided is what your manager's week looks like once you are on the team: how they find out that something you shipped is broken, and whether they hear it from you or from a customer. The material that moves this round is therefore not the launch, it is the week after. Describe how you knew the change was working, which number you watched and for how long, and what you would have reverted to. A project that ends at the word shipped leaves the manager guessing about the part they care about most.

What to demonstrate

  • How a change of yours was verified in production, and whether that check existed before the deploy or was assembled afterwards once something looked wrong
  • Whether the rollback story is specific, including the case where a revert is not enough: once a migration has dropped a column or rewritten data, going back is its own change with its own risk
  • Whether you can describe an incident you caused, how it was found, and how much time passed between it starting and anyone noticing
  • Whether bad news in your account travels early and from you, or consistently arrives via somebody else

How to prepare

  • For your last significant change, write down what you watched after the deploy, for how long, and the number that would have made you revert. If nothing was watched, say that plainly instead of inventing a dashboard.
  • Work out the rollback answer for a change that touched stored data, and be able to say what made it reversible or what you would have had to do instead. That distinction is a level signal on its own.
  • Write the two-minute version of an incident you owned: what broke, how it surfaced, what you did in the first ten minutes, and the change that stopped it recurring. Get the timeline straight, because this is the story most likely to be interrupted with questions.

Peer Developer Interview

Because the format is not fixed, the first job in the room is classification. Listen to the opening question and decide what it is: a probe into work you have already described, a fresh problem to solve now, or a conversation about how you operate. Each wants a different register, and the common failure is forcing a rehearsed structure onto a question that did not ask for it. Running a full design ritual on a ten-minute debugging question reads as not listening. When you cannot tell which it is, ask how long they want to spend and answer at that depth.

What to demonstrate

  • Whether the shape of your answer matches the question, so a yes-or-no gets answered before it is justified and an open prompt gets a direction before a detour
  • Whether you check how much depth is wanted instead of deciding for them, and whether you stop when the answer is complete rather than continuing until someone interrupts
  • Whether you can be redirected in the middle of an answer without restarting it from the beginning
  • Whether a question outside your experience gets an honest boundary followed by reasoning from what you do know, instead of a confident answer with nothing behind it

How to prepare

  • Rehearse one project at three lengths, roughly thirty seconds, three minutes, and a full walkthrough at the depth of a design review, and practise switching between them when someone interrupts mid-telling
  • Have someone ask you five questions of deliberately mixed type in one sitting without telling you the types, and score only whether you identified each one correctly before you started answering
  • Draft the sentence you will use to check depth, along the lines of asking whether the short version is useful here or they want the detail, and use it in a real conversation this week so the day of the round is not its first outing

Technical Challenge

The same problem is scored by two different mechanisms depending on the format, and preparing for one does not cover the other. With a person watching, partial progress is visible and a hint is a correction you can absorb; silence is the expensive failure, because nobody can read a half-written function. With an automated grader there is no partial credit for what you were about to do, nobody to ask, and the worked examples in the prompt are the entire specification. Read them as a contract, down to whether an empty result should be an empty list or no output at all.

What to demonstrate

  • In a live session, whether your commentary tracks what your hands are doing, and whether a hint redirects you or gets defended against
  • In an automated one, whether you cover the cases the examples do not show, since the hidden cases are where the score moves
  • Whether you manage the clock on purpose: abandoning an approach that is not converging while there is still time to write something simpler that finishes

How to prepare

  • Have someone hand you a problem and feed you one deliberately wrong hint. Practise testing it against a concrete case instead of accepting or rejecting it on authority.
  • Do one timed run a week in a plain browser editor with autocomplete, linting and your own snippets switched off, which is closer to what these environments give you
  • For the automated format, write the harness before the solution: a main that feeds the worked examples plus an empty and a single-element case and prints expected against actual, so a wrong submission is caught by you first

Behavioral Interview

This round is deciding whether a change you make without supervision can be allowed to reach production. It is scored on what you knew at the moment you decided, not on how it turned out, so a story that opens with the result and works backwards reads as luck retold as judgement. Say what the options were, what you did not know, what you did to shrink the unknown before committing, and what you accepted as the worst plausible case. The detail that separates answers is a bound: how many users, how much data, and for how long, if you had been wrong.

What to demonstrate

  • Whether the reasoning you give was available at the time you decided rather than after the result came in, since a story whose deciding evidence arrived later describes an outcome and not a judgement
  • Whether you can put units on the exposure (users, rows, minutes of degraded service) and whether the containment you chose actually bounded it: a canary bounds the request path it fronts, while a background job writing to a shared table reaches every user regardless of which version served their requests
  • Whether the reversal path existed before you shipped or was improvised during the incident, and whether it restores state or only stops further damage

How to prepare

  • For your three largest changes, write down the one thing you would have had to be wrong about for it to fail, and what your best estimate of it was on the day you shipped. If you never held an estimate, that is the gap the follow-up questions will find
  • Write the undo procedure for one of those changes as it existed at the time, then mark which steps restore data and which only stop new damage. Turning a flag off or reverting a deploy ends the new writes; rows already written come back only from a copy you kept, and a dropped column comes back empty unless something outside the schema holds the values
  • Rehearse one story from the decision point forward and stop before the outcome, then have someone ask what you would do next. If the story only works with the ending attached, it is an anecdote rather than a decision you can defend

Where candidates lose points

Treating a medical record number or a member ID as a globally unique key and joining on it directly.

These identifiers are unique only within the authority that issued them. Two facilities in one network routinely have the same medical record number for different people, and member IDs get reissued when someone changes plans. Joining on the bare value merges two patients' records, which is the most damaging failure available in this domain, and it passes every test written against a single-facility fixture because the collision only appears once a second source is connected.

Caching an eligibility answer with a long time-to-live and without an as-of date.

Coverage terminates retroactively as a matter of routine: an enrolment file received on the fifth of the month can terminate coverage effective the first. A day-long cache means services are delivered against a 'covered' answer that was already false when it was served, and the denial arrives weeks later. The answer needs to be keyed on person, plan and service date, carry the date it was computed as of, and expire fast enough that the exposure window is a decision rather than an accident.

Never running a concrete value through the code

Trace one small input and one edge input by hand, index by index, out loud. Re-reading your own code catches design mistakes; walking a real value through it catches the off-by-one, the uninitialised accumulator and the loop that never advances.

Assuming the bug is in the framework

Suspect your own code first: read the stack trace top to bottom, check which versions are actually installed rather than which ones you believe are, and reproduce in isolation before blaming a library that thousands of people run daily. When the fault really is upstream, you need that minimal reproduction to say so credibly anyway.

Questions & practice

Merge twelve resource streams into one patient summary page

Medium · k-way merge · heap · pagination

A patient summary fans out to between 8 and 12 resource types. Each returns a network-backed, paged iterator of resource versions sorted by issued_ts descending, up to 200,000 versions per type for one person. Return the 50 most recent current versions across all types, where current means no later version supersedes it within the same logical resource, and a logical resource whose current version is entered_in_error is omitted entirely. You may not materialise the iterators. Give time and space in terms of k types, the result size and the page size.

Approach

  1. k-way merge with a max-heap holding one head per iterator, keyed on issued_ts. Seeding is O(k), each pop is O(log k), so reaching R emitted rows costs O(k + P log k) for P pops, with O(k) heap space plus one page buffered per iterator. Fetching everything and sorting is O(V log V) over V up to 2.4 million versions and drags every page across the network to produce 50 rows.
  2. Suppress with a hash set of logical resource ids already seen, recorded on first sight whether or not that version is emitted. A logical resource has exactly one resource type, so all of its versions arrive on one iterator, and that iterator is descending in issued_ts: the first version you see for a logical resource is its newest. Deciding on first sight and suppressing every later pop for that id is therefore correct in one pass with no lookahead.
  3. Count emits, not pops. A correction-heavy chart can burn many pops per emitted row, so a loop that stops at 50 pops returns a short page. Put a bound on total pops as well, and when it trips, return what you have with a continuation token rather than spinning.
  4. Break issued_ts ties deterministically on (resource type, resource id). Without it two identical requests return two different orderings and the next page silently skips or repeats rows.
  5. Handle entered_in_error at first sight: the erroneous version still supersedes its predecessor, so record the id in the seen set and emit nothing, dropping the whole logical resource instead of falling back to the value it replaced. Recording it is the load-bearing half. Skip it and the next pop re-displays the value a clinician already retracted.
  6. Summarise the budget honestly: the composite p99 is what the user feels, and it is bounded below by the slowest of the k iterators, so the merge fixes the ordering cost but not the fan-out tail.

Follow-up

  • One of the twelve iterators has a p99 of 400ms while the rest return in 20ms. What is your composite p99 and what would you change first?
  • The user pages to rows 51 through 100. How do you resume without re-reading from the top, and what breaks if issued_ts is not unique?
  • One resource type is accidentally returning ascending order. How would your code detect that rather than quietly emitting the oldest rows?

Extract an idempotency key from a raw HL7v2 message

Easy · parsing · idempotency · delimiters

An HL7v2 message arrives as a byte buffer up to 256 KB, segments terminated by carriage return (0x0D), first segment MSH. MSH-1 is the field separator character itself and MSH-2 holds the encoding characters, so splitting the MSH segment on the separator puts MSH-n at index n-1 for n of 2 or more. Return the idempotency key built from MSH-4 sending facility, MSH-3 sending application, MSH-10 message control ID and MSH-7 event timestamp, with escape sequences decoded in each value. Single pass, O(n). Do not hardcode the separator characters.

Approach

  1. Read the delimiters out of the message instead of assuming them: the byte immediately after MSH is the field separator, and the next field's bytes give component, repetition, escape and subcomponent separators in that order. Everything downstream uses those values, because a partner is entitled to send different ones and the common defaults are a convention, not a guarantee.
  2. Verify the first three bytes are MSH before anything else and reject otherwise, since a socket read can begin mid-message. Then bound the segment at the first terminator and split that slice only, applying the n-1 offset that the MSH segment alone requires.
  3. Decode escapes in one left-to-right pass per extracted value: on the escape character, read to the next escape character and map the codes for field, component, subcomponent, repetition and escape back to their literal characters. Treat an unterminated escape as a malformed message rather than dropping the tail silently.
  4. Normalise MSH-7 to UTC before it enters the key. The timestamp may carry an offset or omit one, and two spellings of the same instant must hash identically or the ledger stops deduplicating the moment a partner changes its formatter.
  5. Compose the key as the full tuple, not the control ID alone. The control ID is unique only per sending application and some senders roll the counter over, so facility plus application plus control ID plus instant is the smallest key that survives a rollover.
  6. Cost is O(n) time and O(k) space for the extracted values. Hashing the whole body is also O(n) but cannot distinguish a genuine resend from a corrected retransmission that reuses the control ID, which is a different event.

Worked solution (15 min)

  1. Take the separator characters from bytes 3 through 7 of the buffer and store them.
  2. Slice to the first segment terminator and split on the field separator into tokens.
  3. Read tokens at indices 2, 3, 6 and 9 for MSH-3, MSH-4, MSH-7 and MSH-10.
  4. Unescape each extracted value with the escape character you read in step one, then convert MSH-7 to UTC.
  5. Return the tuple, and the hash of it if the ledger stores a fixed-width key.

Expected result: For a message whose MSH reads MSH, then the encoding characters, then LABAPP, SITE-A, EHR, SITE-B, 20260315142233-0400, an empty field, ORU^R01, MSG00042, P, 2.5.1, the key is (SITE-A, LABAPP, MSG00042, 2026-03-15T18:22:33Z). The local time 14:22:33 at offset -0400 is 18:22:33 UTC.

Verify it yourself

  • Reading MSH-9 with the same rule lands on ORU^R01. If it lands on the empty field or on P, your index is off by one in one direction or the other.
  • Re-encode the same message with a different field separator character and the parser returns the identical key.
  • A value containing the escaped field separator decodes to that literal character and does not cause the field to re-split.
  • Parsing the message twice yields byte-identical keys, and the same control ID from a different facility yields a different key.

Follow-up

  • One partner sends MSH-7 with no timezone offset and another sends it with an offset. What do you store, and what do you compare on?
  • A sender rolls its control ID counter over and restarts from zero. Which component of your key absorbs that, and which message pairs would still collide?
  • Segments arrive separated by line feed instead of carriage return, or with a trailing empty field. Which should the parser accept and which should it negatively acknowledge?

Sum surviving claim versions in one pass over unordered lines

Easy · hash map · versioning · streaming aggregation

You are streamed up to 50 million claim_line records in arbitrary order: claim_id, claim_version, line_number, frequency_code (original, replacement, void), enterprise_person_id, allowed_amount_cents. Every version of a claim shares its claim_id, and a replacement arrives as a higher claim_version. Return total allowed_amount_cents per enterprise_person_id, counting each claim once at its highest version and contributing zero when that version is a void. Amounts are non-negative int64 minor units. Target O(N) time and O(C) space for C distinct claims, single pass, no sort.

Approach

  1. Say the two grains out loud before writing anything: the version lives at claim grain, the money lives at line grain. Every wrong answer here comes from applying a claim-level rule with a line-level filter.
  2. Keep one hash map from claim_id to a small record (best_version, sum_cents, person_id, is_void). Per line: if claim_version is greater than best_version, reset sum_cents to this line's amount and overwrite the person and void flag; if equal, add to it; if lower, discard the line. That reset is what makes the pass order-independent, so a replacement arriving before its original still wins.
  3. Resolve the void at the end, not at ingest. A void only nullifies the claim if it is the surviving version, and zeroing on sight would let an earlier replacement that arrives later resurrect the money.
  4. Fold the C claim records into a person-keyed map in a second phase, O(C). Do not accumulate into the person total during the stream: you cannot subtract a superseded version you have already forgotten.
  5. Cost is O(N) time, O(C) space. The sort-based alternative, group by (claim_id, claim_version) and keep the max, is O(N log N) and needs the set resident; prefer it only when C approaches N and the map will not fit.
  6. Keep cents in int64 throughout. Fifty million lines at a realistic per-line ceiling stay four orders of magnitude below 2^63, so the integer type costs nothing and removes the float drift class entirely.

Follow-up

  • You shard the stream across eight workers. Sharding by hash of claim_id works; what exactly breaks if you shard by enterprise_person_id instead?
  • A replacement arrives for a claim_id whose original never appears in the batch. What does your map produce, and is that the financially correct answer or a reconciliation break?
  • The same claim_id is reused by two different submitting organisations. How does the key have to change, and when would you have noticed?

Denormalise consent into a decision table with a stated staleness bound

Hard · denormalisation · cache invalidation · access control

Access governance answers 'may this requester read this person's data for this purpose' on every identified read — tens of thousands per second at a 5ms p99 — from consent_directive: consent_id, enterprise_person_id, version, scope, purpose_of_use text[], grantee_org_id (NULL meaning all organisations), permit boolean, effective_ts, expires_ts, revoked_ts, status. Evaluating the normalised version history per read misses the budget. Design the denormalised decision store and its key, state the revocation staleness bound in seconds and the mechanism that actually enforces it, and say which normalised rows remain the system of record and why.

Approach

  1. Derive the shape from the read, not from the source table. The read is a point lookup on (person, grantee organisation, purpose), so fan the purpose_of_use array out into one row per purpose and make those three columns the primary key. A GIN index on the array would serve containment queries nobody issues.
  2. Handle the wildcard concretely. grantee_org_id NULL means 'all organisations', and NULL never equals anything, so a nullable column in the key breaks both the primary key and the equality lookup. Store a sentinel org id of 0 for the wildcard and resolve specific-before-wildcard in the lookup, or the wildcard row is silently unreachable.
  3. Make refusal a row, not an absence. An explicit permit = false must outrank a permit for the same key, so carry a precedence rank (specific grantee beats wildcard, refusal beats permit at equal specificity) and resolve with ORDER BY rank LIMIT 1. Default deny when nothing matches, so a rebuild failure fails closed.
  4. Separate the two staleness mechanisms and be precise about which one is the bound. The in-process decision cache has a TTL; a revocation also publishes an invalidation. The publish shortens the typical case to milliseconds, but it can be lost, so the guaranteed bound is the TTL alone — quote that number, for example 30 seconds, and justify it against the cache hit rate you need to hold 5ms p99.
  5. Evaluate expires_ts at read time against the request clock rather than baking the decision. A cached permit whose expires_ts has passed is wrong regardless of invalidation, because no event fires when a timestamp simply goes by.
  6. Keep consent_directive as the system of record: audit must reconstruct the directive in force at any past instant, the decision table holds only current state, and a corrupted decision table must be rebuildable by replaying the directives. Bound the write amplification by keeping grantees at organisation granularity and purposes a closed code set, so one directive expands to a known small number of rows rather than an unbounded cross product.

Follow-up

  • Break-glass must always succeed and must be unmistakably marked. Where does it sit relative to this lookup, and what stops it from becoming the quiet default path?
  • The invalidation bus is down for thirty minutes. Walk through what a revoked person's data exposure looks like minute by minute, and what the backlog does when the bus returns.
  • Where does the access audit record get written so that an audit-store outage degrades neither the log's completeness nor the read's availability?

Add a backfilled NOT NULL column to a live encounter table

Hard · online migration · ddl locks · backfill

encounter holds 900 million rows and is written continuously by the admit/discharge/transfer consumer. You must add admission_source_code text, backfill it per row from a lookup on the source message archive, constrain it to a fixed code set, make it NOT NULL, and add an index on (facility_id, admit_ts) — with no write outage and no statement blocked longer than one second. Give the ordered steps, the lock level each takes, why the obvious single ALTER is unavailable here, and the session setting that stops a DDL statement from stalling everything behind it.

Approach

  1. Say why the shortcut does not apply. Since PostgreSQL 11, ADD COLUMN with a constant default is metadata-only and cheap, but this value is derived per row from an archive lookup, so there is no constant to store and the table must be written row by row regardless.
  2. Add the column nullable first: catalog-only, ACCESS EXCLUSIVE for microseconds. The danger is not the statement's duration but the lock queue — a pending ACCESS EXCLUSIVE blocks every reader that arrives behind it, so a long-running report turns a microsecond DDL into a multi-minute outage. SET lock_timeout = '1s' before the ALTER and retry on failure; that converts a potential outage into a retried statement.
  3. Deploy dual-write before backfilling. The consumer must populate the column on every insert and transfer from that moment, or the backfill chases a moving tail forever.
  4. Backfill in bounded batches over primary-key ranges, a few thousand rows per transaction with a pause between, restricted to WHERE admission_source_code IS NULL so it is resumable and idempotent. One giant UPDATE holds a transaction open for hours, pins the xmin horizon so autovacuum cannot clean anything, and bloats the table by a full row version per updated row.
  5. Add the value constraint as CHECK (...) NOT VALID first — ACCESS EXCLUSIVE, no scan — then ALTER TABLE ... VALIDATE CONSTRAINT, which takes only SHARE UPDATE EXCLUSIVE and scans without blocking reads or writes.
  6. Reach NOT NULL without a blocking scan: add CHECK (admission_source_code IS NOT NULL) NOT VALID, VALIDATE it, then SET NOT NULL, which on PostgreSQL 12 and later uses the validated constraint as proof and skips the full scan; drop the now-redundant CHECK afterwards. Build the index with CREATE INDEX CONCURRENTLY, which takes SHARE UPDATE EXCLUSIVE, makes two passes plus a wait for concurrent transactions, cannot run inside a transaction block, and leaves an INVALID index behind on failure that must be dropped and rebuilt.

Worked solution (45 min)

  1. Write the runbook as numbered statements with the lock level annotated next to each, and mark which ones may be retried safely.
  2. Set lock_timeout = '1s' and statement_timeout deliberately high for the VALIDATE steps, since they are long but non-blocking.
  3. Write the batch loop with an explicit key range and a resume predicate, plus a query that reports remaining NULLs so progress is observable.
  4. Sequence NOT NULL as CHECK NOT VALID, VALIDATE, SET NOT NULL, DROP CONSTRAINT, and state which of those four takes ACCESS EXCLUSIVE.
  5. Run CREATE INDEX CONCURRENTLY outside any transaction block and monitor pg_stat_progress_create_index through its phases.
  6. Write the rollback for each step, including the DROP INDEX CONCURRENTLY for a failed build.

Expected result: 1) SET lock_timeout='1s'; ALTER TABLE encounter ADD COLUMN admission_source_code text; (ACCESS EXCLUSIVE, catalog-only, retry on lock timeout). 2) deploy dual-write. 3) batched UPDATE ... WHERE encounter_id >= :lo AND encounter_id < :hi AND admission_source_code IS NULL, a few thousand rows per transaction. 4) ALTER TABLE encounter ADD CONSTRAINT encounter_admission_source_chk CHECK (admission_source_code IN (...)) NOT VALID; (ACCESS EXCLUSIVE, no scan) then ALTER TABLE encounter VALIDATE CONSTRAINT encounter_admission_source_chk; (SHARE UPDATE EXCLUSIVE, scans concurrently). 5) ADD CONSTRAINT ... CHECK (admission_source_code IS NOT NULL) NOT VALID; VALIDATE; ALTER COLUMN ... SET NOT NULL (PG 12+ skips the scan using the validated constraint); DROP the helper constraint. 6) CREATE INDEX CONCURRENTLY idx_encounter_facility_admit ON encounter (facility_id, admit_ts); outside any transaction block.

Verify it yourself

  • SELECT count(*) FROM encounter WHERE admission_source_code IS NULL returns 0 before SET NOT NULL is attempted.
  • SELECT conname, convalidated FROM pg_constraint WHERE conrelid = 'encounter'::regclass shows convalidated = true for both constraints.
  • SELECT indexrelid::regclass, indisvalid FROM pg_index WHERE indrelid = 'encounter'::regclass shows indisvalid = true for the new index; false means the concurrent build failed and left a partial index.
  • Throughout the migration, pg_stat_activity shows no session with wait_event_type = 'Lock' held for more than one second, and the ADT consumer's write latency is unchanged.

Follow-up

  • CREATE INDEX CONCURRENTLY has been running for four hours and you need to cancel it. What state is the index left in, how do you detect it, and what do you run next?
  • Your backfill is at 40% and the replica is 90 seconds behind. What do you change, and what do you measure to decide the new batch size?
  • The code set gains a value two weeks later. Does your CHECK constraint or a lookup table with a foreign key make that change cheaper, and what does each cost on the write path?

What are the main differences between PHP versions (e.g., 5, 7, and 8) and how would you approach a migration?

Medium

Approach

  1. Clarify what is being asked and what a complete answer contains.
  2. Say what you would check first and why it is the highest-information step.
  3. Work from the requirement backwards to the design.

Follow-up

  • What assumption would you test first?
  • How would you know your answer was wrong?

How do you manage buffering or output control in web applications?

Medium

Approach

  1. State your assumptions explicitly before working the problem.
  2. Work from the requirement backwards to the design.
  3. Clarify what is being asked and what a complete answer contains.

Follow-up

  • What assumption would you test first?
  • How would you know your answer was wrong?

Set rate limits across registration, batch ingest and bulk export

Medium · rate limiting · backpressure · prioritisation

Three callers share the identity resolution and longitudinal record APIs: a registration desk making 1-3x10^3 interactive match calls a second against a 200ms budget, a nightly ingest replaying millions of messages in a burst, and partner bulk exports walking whole panels. One global limit either starves registration during the batch window or stretches the batch past morning. Define the limiting scheme - the key, the algorithm, the reserved capacity, the response when a caller is limited - and state precisely what each of the three callers does when it is limited.

Approach

  1. Reject a single global limit, then reject per-IP: the batch runs from a handful of hosts and the desk sits behind a shared egress, so IP is simultaneously too coarse and too fine. Key on the authenticated principal plus a traffic class bound to the credential, never a class the caller declares in a header it controls.
  2. Pick the algorithm from the traffic shape. A fixed-window counter admits up to twice the limit across a window boundary, which is exactly the top-of-hour moment the desk bursts; a token bucket with a burst allowance, or a sliding-window counter, does not. The desk needs burst tolerance; the batch needs a steady ceiling.
  3. Give the classes different treatment rather than different numbers of the same thing: a reserved floor of capacity the interactive class cannot be pushed below, the remainder shared by batch and export, and the batch class shedding first under pressure. This is a scheduling decision - the class that can wait is the one that waits.
  4. Answer with 429 plus Retry-After in delta-seconds and the limit, remaining and reset headers, so the caller need not invent a backoff. Keep 429 distinct from 503: 429 means try later and the request did no work, 503 means the service is unhealthy. Conflating them makes correct client behaviour impossible to write.
  5. Write each caller's behaviour, because a limit without a documented client response only relocates the failure. The desk degrades to deterministic-only matching and flags the registration for review rather than blocking a patient; the ingest applies backpressure to its consumer, since the messages are durable and delay is free while loss is not; the export sleeps Retry-After and resumes from its cursor. Then state where the counter lives: a shared store with one atomic increment-and-expire per decision, sized so the limiter is not the bottleneck at tens of thousands of decisions a second, and with a stated fail-open or fail-closed behaviour when it is unreachable.

Worked solution (25 min)

  1. Write the three traffic profiles as numbers: request rate, burst shape, latency budget, and what each caller can tolerate on refusal.
  2. Choose the key, show where the class comes from on the credential, and write the check that stops a caller self-declaring.
  3. Work the fixed-window boundary arithmetic that admits twice the limit, then the token-bucket or sliding-window version that does not.
  4. Write the full 429 response with every header, and the exact backoff each of the three callers implements.
  5. Write the reserved-capacity rule and trace all three classes at 150 percent of total demand.

Expected result: Limit keyed on (principal, traffic_class) with the class bound to the credential rather than the request. Token bucket with per-class burst; the interactive class holds a reserved floor; batch sheds first. 429 with Retry-After in delta-seconds plus limit, remaining and reset headers, kept distinct from 503. On 429 the desk degrades to deterministic-only matching and flags for review, the ingest applies backpressure to its durable consumer, the export sleeps and resumes from its cursor. Counters in a shared store, one atomic increment-and-expire per decision, with a stated behaviour when that store is unreachable.

Verify it yourself

  • No caller can move itself into the reserved class by changing a request.
  • Two adjacent windows cannot together admit more than the intended rate.
  • At 150 percent demand the interactive class still meets its 200ms budget.
  • Every 429 tells the caller how long to wait without the caller guessing.

Follow-up

  • The batch runs under the same credential as an interactive tool. How do you separate them?
  • The limiter's shared store goes down. Does traffic fail open or closed, and what is the argument for your choice here specifically?
  • One partner's export is now 40 percent of read load while staying inside its limit. What do you change?

Duplicate patients created quietly by a matcher timeout fallback

Hard · partial outage · fallback policy · identity resolution

Over two days the manual duplicate-review queue grew eight-fold, and a clinician reports that a patient's prior results are missing from the chart. No error-rate alert fired: registration calls the identity resolution service with a 250ms timeout and, on timeout, creates a new enterprise person and returns 200. person_identity_link records match_score, link_status, decided_by and decided_at per link version. The identity service is operated by another team. Give the ordered investigation, what you can prove alone, and what you must ask them for.

Approach

  1. Work backwards from the damage rather than forwards from a dashboard. Take one duplicate pair, read both person_identity_link rows, and examine decided_by and match_score: a matcher version string, a user id, or the fallback's marker tells you which code path asserted the identity, and a NULL match_score means a deterministic link rather than a probabilistic one. If the duplicates were created by the fallback path, one query has given you the mechanism.
  2. Correlate decided_at for fallback-created links against your own client-side timeout counter, minute by minute. A creation rate that tracks the timeout rate with no lag is causal; one that does not sends you back to the first step rather than letting you build on a coincidence.
  3. Determine the failure shape. Is the timeout rate uniform across identity-service instances or concentrated on a few? Uniform means capacity or a global regression; concentrated means a partial outage — one degraded shard or replica — and the remedies differ. You can often infer this alone if responses carry an instance identifier; if they do not, that is the first thing to request.
  4. Name the real defect. The latency is the trigger; the incident is a fallback that converts unknown into new person. A 200 carrying a fabricated identity is worse than a 503, because it silently splits a chart, and the missing prior results are the direct consequence of that split rather than a separate problem.
  5. Stop the bleeding without blocking registration: on timeout, register under a provisional identity with link_status = potential_duplicate and route it to review. Clinical safety requires that registration proceeds; nothing requires that the system assert a new person on the strength of a timeout.
  6. Repair, then state the ask precisely. Because links are versioned and merges are reversible, remediation means re-running the matcher offline over the window's registrations and generating merge candidates — never rewriting downstream foreign keys. From the other team, request per-instance p99 and timeout counts for the window plus their deploy and index-maintenance log, and say which of your conclusions depends on that answer.

Follow-up

  • The missing results are attached to the losing identity. Describe the read path that surfaces them before the merge is approved, and what a clinician sees while the pair is still pending.
  • Your timeout is 250ms against a 200ms budget. What is the right value, and what happens to duplicate creation and to registration latency if you double it?

Suggested practice checklist

For a candidate senior enough that the loop turns on design and judgement rather than on whether the coding round gets finished. Five days build one system properly and then stress it; coding gets a single maintenance day, on the assumption that the risk at this level is an unexamined tradeoff rather than a missed algorithm.

Day 1 — Numbers before diagrams

  • Build your own reference card of the figures you will re-derive all week: bytes for a realistic record, requests per second implied by a given daily active count, and the storage that a year at a given write rate produces. Derive each one rather than copying it, because the derivation is what survives a follow-up.
  • Turn one product statement into capacity requirements. From ten million daily users at four writes and forty reads each, state the peak-to-average factor you are assuming and why, then produce peak write QPS, peak read QPS and a year of storage.
  • Write the two numbers whose order of magnitude changes the design, the read-to-write ratio and the working-set size against memory per node, and state the threshold at which each one flips your answer.

Deliverable: A one-page numbers card and one worked capacity estimate with every assumption written down.

Day 2 — One system, from requirements to schema

  • Spend the first ten minutes producing only functional requirements, non-functional targets with numbers attached, a p99 latency, a durability expectation, a consistency requirement, and an explicit out-of-scope list.
  • Define the interface before the boxes: the three or four endpoints, their parameters, what each returns, and which of them are idempotent.
  • Write the data model, then write the single access pattern that justifies it, and state what the schema would have to become if the dominant access pattern were the other one.

Deliverable: One design carried to endpoint-and-schema depth, with non-functional targets expressed as numbers and a written out-of-scope list.

Day 3 — The consistency you are actually buying

  • Write out what a client sees under asynchronous replication when its write commits on the leader and its next read is served by a lagging follower, then write the two fixes, pinning that session's reads to the leader for a bounded window or carrying a version token the replica must reach, and the cost of each.
  • Work the quorum arithmetic on paper for N of three with W and R of two, and separate what R + W > N does guarantee, that any read set intersects any write set, from what it does not: on its own it is not linearizability, and a sloppy quorum that accepts writes on nodes outside the preference list breaks even the intersection.
  • Take two storage choices with different defaults, a single-leader relational store committing synchronously and a quorum-replicated store that converges eventually, and write the specific product behaviour that would be wrong under each, rather than a general statement about which is stronger.

Deliverable: A page separating what quorum overlap guarantees from what it does not, with one concrete product misbehaviour attached to each gap.

Day 4 — Failure is the design

  • For one write path, work through the case where the client times out after the server has already committed, then design the idempotency key: who generates it, how long it is retained, and what the duplicate request returns.
  • Express the retry policy as parameters rather than as a word: maximum attempts, base delay, backoff factor, jitter, and which error classes are retried at all. Then state why retrying a non-idempotent write without a key is a correctness bug and not merely waste.
  • Compute the fan-out effect on tail latency. If a request waits on ten backends and each independently exceeds its p99 one percent of the time, the chance at least one is slow is 1 - 0.99^10, about ten percent. Then write why independence is the optimistic assumption and what correlates them in practice.
  • Name the backpressure mechanism for one queue or one dependency in the design, a bounded queue with shedding or a concurrency limit, and write what the caller is told when it engages.

Deliverable: One write path with an idempotency design, a parameterised retry policy, and a written tail-latency calculation with its assumption named.

Day 5 — Scaling the hot path

  • Choose cache-aside or write-through for one read path and write the staleness window each produces, then name the invalidation event and what the system does when that event is lost.
  • Design against the stampede: either coalesce requests so only one recomputes a missing key, or refresh early with jittered expiry, and write why identical TTLs on keys populated in the same moment produce a synchronised expiry and a thundering herd.
  • Shard one table by a key you choose, then answer the two questions that break the choice: which queries now require a scatter-gather, and what happens to the distribution when one tenant is a hundred times larger than the median.
  • Write the cost of adding a node under plain modulo placement, where nearly every key moves, against consistent hashing, where roughly one key in n+1 moves, and state what virtual nodes are for.

Deliverable: A caching and sharding decision for one path, each with its failure mode and its rebalancing cost written beside it.

Day 6 — Keep the coding hand in, at the bar that applies to you

  • Solve one medium problem in thirty minutes, then spend twenty more making it production-shaped: named invariants, validation at the boundary, and errors that distinguish a caller mistake from an internal fault.
  • Write the tests you would require of a colleague's version of that function: one for empty input, one for the boundary, and one for the case the implementation is most likely to get wrong.
  • Read a piece of your own code from six months ago and write the change you would ask for, phrased as you would actually phrase it in review.

Deliverable: One problem hardened to review standard, with its test list and one written review comment.

Day 7 — Defend it while being interrupted

  • Run a forty-five-minute design mock with an interviewer briefed to change a requirement halfway, a tenfold traffic increase or a new strict consistency requirement, and to push on one number you estimated.
  • Rehearse the two sentences a senior loop is listening for: naming the tradeoff you are choosing against and why, and saying what you would measure to learn that the choice was wrong.
  • Prepare the design you regret: a real decision, the constraint that produced it, what it cost, and what you changed afterwards.

Deliverable: Mock notes recording how the design changed under the new requirement, plus a written account of one regretted decision.

What the behavioural round is actually measuring

Every story you tell gets read for blast radius and judgement: what could have broken, who else it touched, what you knew at the moment you decided. Nobody can audit your code in an hour, so they audit your reasoning instead. Pick work where the call was genuinely yours and the consequences were real enough to remember.

How do you support or mentor junior developers on your team?

Medium · behavioural and engineering judgement

Approach

  1. Name the disagreement and how you resolved it with evidence.
  2. State the situation in two sentences and spend the rest on the reasoning.
  3. Pick a story where you made the decision, not one where you watched it.

Follow-up

  • What would you do differently if you ran that again?
  • What did you decide not to do, and why?

How do you communicate complex technical information to non-technical stakeholders?

Medium · behavioural and engineering judgement

Approach

  1. Close with what you would do differently, concretely.
  2. Pick a story where you made the decision, not one where you watched it.
  3. Name the disagreement and how you resolved it with evidence.

Follow-up

  • What would you do differently if you ran that again?
  • How did you know your change caused the improvement?

What is your experience with Cypress for testing automation?

Medium · behavioural and engineering judgement

Approach

  1. Give the blast radius: what could have broken, and what you measured.
  2. Pick a story where you made the decision, not one where you watched it.
  3. State the situation in two sentences and spend the rest on the reasoning.

Follow-up

  • What did you decide not to do, and why?
  • How did you know your change caused the improvement?

Tell me about a time you had a significant disagreement with a teammate; how did you resolve it?

Medium · behavioural and engineering judgement

Approach

  1. Pick a story where you made the decision, not one where you watched it.
  2. State the situation in two sentences and spend the rest on the reasoning.
  3. Name the disagreement and how you resolved it with evidence.

Follow-up

  • What would you do differently if you ran that again?
  • How did you know your change caused the improvement?