Interview guides · Illumina
Illumina Software Engineer Interview Questions & Guide 2026
Illumina software engineer prep on partial failure: a timed-out request has an unknown outcome, not a failed one.
The 60-second brief
As a Software Engineer at Illumina, you will build the critical software platforms and pipelines that power global genomics. Illumina instruments and analysis software process vast quantities of biological data, transforming complex sequencing signals into actionable biological insights. Whether you are developing low-level hardware control systems, optimizing high-throughput data analysis engines like DRAGEN, or building web applications and cloud infrastructure, your engineering work directly enables breakthrough medical research, cancer diagnostics, and personalized medicine.
If the seat owns a service boundary, scope your preparation toward failure behaviour rather than topology. Retrying over an at-least-once channel produces duplicates by construction, so a retry policy is only as safe as the idempotency key underneath it.
Focus areas
- Version clinical results instead of updating rows in place
- Authorise every identified read against current consent
- Make HL7 and X12 ingestion idempotent under replay
Overview
The scope of software engineering at Illumina spans a diverse ecosystem. Engineers design software for primary analysis (signal processing and base calling), secondary analysis (variant calling and sequence alignment), and enterprise cloud solutions that store and interpret genomic datasets. This requires writing high-performance, fault-tolerant, and highly reliable code in environments where precision and efficiency are paramount. You will collaborate closely with cross-functional teams including bioinformaticians, systems engineers, fluidics specialists, and product managers to deliver end-to-end biological platforms.
To succeed in this role, you must pair strong core computer science fundamentals with an eagerness to understand domain-specific challenges. Illumina values software engineers who can solve intricate algorithmic problems, design scalable architectures, and maintain rigorous software quality standards. If you are energized by solving challenging engineering problems that directly improve human health, this position provides a unique and impactful platform.
The Illumina interview process
Illumina candidates report 3 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.
Phone Screening
Before anything technical happens, someone has to decide which rung of the ladder your loop is calibrated to, and that decision sets the bar for every round after it. It comes from how you describe scope, not from your title, because titles do not convert cleanly between companies. The weak version of the answer is team size and years. The strong version names the largest change you shipped where nobody reviewed the design, what would have broken if you had been wrong, and what you were paged for. Get the level said out loud on this call, because the range and the loop both follow from it.
What to demonstrate
- Whether the scope in your own account maps onto a level the team actually has an opening at, so a mismatch ends the process cheaply rather than after four interviewers have spent a day
- Whether your title needs re-mapping: the same word describes very different amounts of independent decision-making at a twenty-person company and a ten-thousand-person one
- Whether your compensation expectation can be filled at that level in the structure the role pays in, which is why the number gets asked for before any engineer is scheduled
How to prepare
- Write down two changes from the last two years: the largest one you designed with nobody reviewing the design, and the largest one where someone more senior did. Lead with the first when scope comes up, and be ready to say which parts of the second were yours
- Ask which level the loop is calibrated to and what changes at the level above it, then plan your weeks from that answer rather than from the posting
- Settle a total-compensation range beforehand with the split named, base against bonus against equity and its vesting period, so a question about numbers gets a number instead of the word market
Technical Interviews
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 Evaluations
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.
Upserting on the order or result identifier, so a correction overwrites the original row.
It makes the question 'what did the clinician see at 14:02' unanswerable, which is exactly what an incident review or a legal hold asks. It also leaves downstream consumers that already acted on the preliminary value with no correction event to react to, because the state transition was collapsed into a single mutated row and never emitted.
Going silent while thinking
Narrate the candidates and why you are discarding them, even in fragments: sorting first would make this a two-pointer scan, but it destroys the original indices, which the output needs. From the other side of the table, a candidate thinking hard and a candidate stuck are indistinguishable until one of them speaks.
Treating a network call as though it were a local function call
A remote call can be slow, fail, or return after you stopped waiting, so name the timeout, the retry policy, and what the caller sees while the dependency is down. A call with no timeout turns one slow dependency into an exhausted thread or connection pool in every service upstream of it.
Questions & practice
Reverse a doubly-linked list in-place.
Medium · data structures and algorithms
Approach
- Walk one small example through your approach before writing the whole thing.
- Restate the input: its shape, its size, and what is guaranteed about it.
- State the target complexity and say which constraint rules the naive version out.
Follow-up
- Which test case would catch an off-by-one here?
- What is the worst case, and how likely is it on real data?
Write a function to find the largest sum contiguous subarray from an input list of integers.
Medium · data structures and algorithms
Approach
- Walk one small example through your approach before writing the whole thing.
- State the target complexity and say which constraint rules the naive version out.
- Name the brute-force solution and its complexity before improving on it.
Follow-up
- How does this change if the input no longer fits in memory?
- Which test case would catch an off-by-one here?
Given an array of integers from 1 to 100 with one missing number, find the missing value. How does your solution scale if the range expands to 1 to 1,000,000?
Medium · data structures and algorithms
Approach
- State the target complexity and say which constraint rules the naive version out.
- Restate the input: its shape, its size, and what is guaranteed about it.
- Walk one small example through your approach before writing the whole thing.
Follow-up
- Which test case would catch an off-by-one here?
- What is the worst case, and how likely is it on real data?
Return a list of files and folders matching a given filter criteria within a directory hierarchy, handling nested structures recursively.
Medium · data structures and algorithms
Approach
- Walk one small example through your approach before writing the whole thing.
- Restate the input: its shape, its size, and what is guaranteed about it.
- Choose the data structure from the access pattern, not from familiarity.
Follow-up
- Which test case would catch an off-by-one here?
- What is the worst case, and how likely is it on real data?
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
- 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.
- 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.
- 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.
- 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.
- 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.
- 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)
- Take the separator characters from bytes 3 through 7 of the buffer and store them.
- Slice to the first segment terminator and split on the field separator into tokens.
- Read tokens at indices 2, 3, 6 and 9 for MSH-3, MSH-4, MSH-7 and MSH-10.
- Unescape each extracted value with the escape character you read in step one, then convert MSH-7 to UTC.
- 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?
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
- 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.
- 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.
- 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.
- 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.
- 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.
- 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?
Reproduce and fix a lost update on a deductible accumulator
Medium · isolation levels · lost update · lock contention
An accumulator row holds deductible_applied_cents bigint and plan_deductible_cents bigint, keyed by (enterprise_person_id, plan_id, benefit_year). The adjudicator opens a transaction, SELECTs the row, computes the member's share in application code, then UPDATEs the row to the absolute new total it computed, all under PostgreSQL's default READ COMMITTED. Two claim lines for one member adjudicate concurrently: both charge the member deductible, but the accumulator advances by only one of the two amounts, so the member is billed deductible again on a later line after the plan deductible has already been met. Write the exact two-session interleaving that produces it. Then give three fixes, each naming the lock or isolation level, the SQLSTATE you must handle, and the throughput cost.
Approach
- State the anomaly precisely. READ COMMITTED takes a fresh snapshot per statement, so it prevents dirty reads but permits a lost update when a transaction reads a value, computes outside the database, and writes back an absolute result. The vulnerable window is the round trip through application code between two statements, not the transaction boundary — the same logic expressed as one relative UPDATE is safe at this very isolation level.
- Write the interleaving as an ordered script both sessions can be replayed from, with the commit points marked, and make both writes absolute (SET deductible_applied_cents = :computed_total). The point is not that two writes happen — it is that the second write stores a total computed from a value that had already been superseded by the time it landed.
- Fix one, SELECT ... FOR UPDATE on the read: the second session blocks on the row lock and, under READ COMMITTED, re-reads the newest committed version when it unblocks, so its computation starts from the winner's total. No serialization error to handle. Cost is serialised throughput per member and a lock held for the transaction's whole duration, so nothing inside may call an external payer.
- Fix two, REPEATABLE READ or SERIALIZABLE with a retry loop: in PostgreSQL, REPEATABLE READ aborts the second writer of a row with SQLSTATE 40001, 'could not serialize access due to concurrent update'; SERIALIZABLE additionally aborts on read/write dependencies detected by SSI, also under 40001. The retry re-runs the whole transaction, so the claim application must be idempotent or keyed by claim_line_id, or a retry double-applies.
- Fix three, single-writer partitioning: route by hash(enterprise_person_id) to one consumer per partition, so no two transactions ever touch one accumulator. No locks, no retries; the cost is head-of-line blocking behind a slow claim, rebalancing during deploys, and losing the ability to scale a hot member.
- Name the cheapest option and its real catch. A single UPDATE ... SET deductible_applied_cents = LEAST(plan_deductible_cents, deductible_applied_cents + :amt) ... RETURNING deductible_applied_cents does not lose the update: under READ COMMITTED an UPDATE that hits a concurrently updated row blocks, then re-evaluates its expressions against the newly committed version, so both increments land. The catch is that the member's share is the delta this statement actually applied, which is the returned value minus the row's pre-image — and RETURNING does not expose the pre-image before PostgreSQL 18's OLD/NEW aliases. On earlier versions you must read that pre-image under the same row lock, which puts you back in fix one with a shorter critical section. The one-statement form is a clean fix only when the caller does not need the delta.
Worked solution (35 min)
- Open two psql sessions against a scratch database and seed one accumulator at deductible_applied_cents = 40000, plan_deductible_cents = 100000.
- Run the interleaving by hand with both writes absolute, and confirm the row ends at 70000 while the two lines between them charged the member 55000 of deductible — the serial answer is 95000, so 25000 of applied deductible has been lost.
- Run the control: repeat the identical interleaving with both UPDATEs written as SET deductible_applied_cents = deductible_applied_cents + :amt, and confirm the row ends at 95000. That form does not lose the update at READ COMMITTED, which is how you know the defect is the read-modify-write in application code rather than the isolation level by itself.
- Re-run the absolute version with FOR UPDATE on both SELECTs and confirm session two blocks, re-reads 65000, and writes 95000.
- Re-run both sessions at REPEATABLE READ and capture the exact error text and SQLSTATE from the losing session.
- Write the retry wrapper and show what makes the retry safe: a uniqueness key on (accumulator_key, claim_line_id) in an application ledger, so a re-run of an already-applied line is a no-op rather than a second application.
- Write the one-statement LEAST form with RETURNING and say where the member's share now comes from, given RETURNING carries no pre-image before PostgreSQL 18.
Expected result: Seed: deductible_applied_cents = 40000, plan_deductible_cents = 100000. Interleaving: T1 BEGIN; SELECT deductible_applied_cents -> 40000; application computes line A's deductible share as 25000 and the new total as 65000. T2 BEGIN; SELECT -> 40000, because T1 has not committed and READ COMMITTED shows only committed rows; application computes line B's share as 30000 and the new total as 70000. T1 UPDATE accumulator SET deductible_applied_cents = 65000 WHERE key...; COMMIT. T2 UPDATE accumulator SET deductible_applied_cents = 70000 WHERE key...; it blocks on T1's row lock, and on unblocking writes its absolute 70000 over the committed 65000; COMMIT. The row reads 70000 while the two lines charged the member 55000, so the serial answer is 95000 and T1's 25000 is lost; the accumulator understates by 25000 and the member is billed deductible again after the 100000 is already met. The write must be absolute for this to happen — SET deductible_applied_cents = deductible_applied_cents + :amt blocks, re-reads the committed row and applies both increments. Fixes: (1) SELECT ... FOR UPDATE on the read, no SQLSTATE to handle, serialises per member for the transaction's whole duration and therefore forbids external calls inside it; (2) REPEATABLE READ or SERIALIZABLE plus retry on SQLSTATE 40001 ('could not serialize access due to concurrent update' under REPEATABLE READ; SERIALIZABLE raises the same SQLSTATE for read/write dependencies), which needs an idempotency key because the whole transaction re-runs; (3) hash-partition claims by enterprise_person_id to one writer per partition, zero contention at the cost of head-of-line blocking and rebalancing.
Verify it yourself
- The unfixed script reproduces 70000 on a real database rather than on paper, and replaying the same two lines serially reproduces 95000.
- The control run with in-SQL relative increments, under the identical interleaving, ends at 95000 — that isolates the defect to the application-side read-modify-write and not to READ COMMITTED itself.
- With FOR UPDATE, session two's SELECT visibly blocks until session one commits, and the final value is exactly 95000.
- At REPEATABLE READ the losing session reports SQLSTATE 40001 with 'could not serialize access due to concurrent update', and its transaction is left in an aborted state requiring ROLLBACK.
- Applying the same claim_line_id twice through the retry path leaves the accumulator unchanged on the second attempt.
Follow-up
- A claim is reversed six months later under a plan design that has since changed. What amount does the reversal subtract, and where is that number stored?
- Your retry loop hits 40001 repeatedly for one member during a batch window. What is the backoff, and at what point do you stop retrying and pend the claim?
- Which of the three fixes survives a retroactive eligibility change that invalidates everything applied in the last month, and what does the rebuild look like?
What software design patterns would you apply when refactoring a monolithic application into modular microservices?
Medium · system design and architecture
Approach
- Name the read and write paths separately; they rarely have the same bottleneck.
- State the consistency you need, and where you are willing to be stale.
- Name the failure you are designing for, then the recovery path.
Follow-up
- What would you drop to keep the system up under load?
- How does this behave when that dependency is down for an hour?
Explain how you would prevent race conditions and manage state synchronization in multi-threaded application environments.
Medium · system design and architecture
Approach
- State the consistency you need, and where you are willing to be stale.
- Name the failure you are designing for, then the recovery path.
- Name the read and write paths separately; they rarely have the same bottleneck.
Follow-up
- What breaks first when traffic grows ten times?
- What would you drop to keep the system up under load?
Describe key C# and.NET or Python concepts, such as memory management, garbage collection, and async/await mechanisms.
Medium
Approach
- Work from the requirement backwards to the design.
- State your assumptions explicitly before working the problem.
- Say what you would check first and why it is the highest-information step.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
Authorising every identified read with bounded revocation
Medium · authorization-cache · revocation · outbox · availability
Access governance is invoked on every identified read — tens of thousands of decisions per second — and must hold a sub-5ms p99 while evaluating consent_directive (scope, purpose_of_use, grantee_org_id, permit, effective_ts, expires_ts, revoked_ts, status) against organisational policy and relationship to the person. Every decision emits an audit record. Break-glass must always succeed and always be unmistakably marked. Give the cache design, the bound in seconds between a revocation and reads stopping, the mechanism that enforces that bound, and the system's behaviour during a thirty-minute audit-store outage.
Approach
- Put the decision cache in-process, keyed on requester, enterprise_person_id, purpose_of_use and grantee organisation, with the evaluated directive version in the value. A network hop cannot hold a 5ms p99 at that rate once queueing is included, so the local cache is what makes the budget reachable — and it is also what creates the staleness you now have to bound rather than hand-wave.
- Make the bound a hard number enforced by expiry, and treat push invalidation as an optimisation on top. A revocation bus gives sub-second propagation when it works, but a dropped message or a reconnecting subscriber leaves one node stale indefinitely; the entry lifetime is the only ceiling that survives the bus being broken. A 30-second lifetime states the bound as 30 seconds and the bus makes the common case under one.
- Write the access decision and its audit row together into a local outbox table in the same transaction, and ship asynchronously. That single placement is the whole answer to the outage: the read path never touches the audit store, so thirty minutes of downtime produces a growing local backlog and no clinical impact, and the backlog drains in order afterwards.
- Size the outbox so it is a bounded promise rather than an assumption: state the disk the backlog consumes at peak decision rate for the longest outage you will tolerate, and define the behaviour when it fills. Silently dropping records is the one unacceptable option, because the gaps land precisely in the incidents the log exists to reconstruct.
- Implement break-glass as a separate path that evaluates no policy, always permits, writes a distinguished audit record carrying the asserted reason, and enqueues a review with a committed turnaround. The deterrent is the review, not friction in the path — friction during an emergency is a clinical safety problem.
Worked solution (30 min)
- Write the cache key and value, then state the revocation bound in seconds and name the single mechanism that guarantees it when the bus is down.
- Write the outbox schema and the transaction that commits the decision and its audit row together.
- Compute backlog size at peak decision rate over thirty minutes, and define the full-outbox behaviour.
- List the fields of the access audit record and justify each against a specific question an incident review asks.
- Trace a break-glass read end to end, marking where it diverges from the normal path.
Expected result: An in-process decision cache with a stated lifetime as the hard revocation bound and push invalidation as an optimisation, decision and audit committed together to a local outbox and shipped asynchronously, a sized backlog with defined full behaviour, and a break-glass path that always permits, is distinctly marked, and feeds a reviewed queue.
Verify it yourself
- With the invalidation bus stopped entirely, reads still cease within the stated bound.
- With the audit store unreachable for thirty minutes, read latency and error rate are unchanged and no audit record is lost.
- Every decision in a sampled window has exactly one audit record, break-glass included.
- Break-glass records are distinguishable by a field, not by inference from the absence of a consent match.
Follow-up
- The audit backlog reaches its disk limit at minute forty of the outage. What does the system do, and what do you tell the compliance owner?
- One person's revocation must take effect immediately rather than within the stated bound. What do you add without lowering the global entry lifetime?
Patient summary p99 blows up after version resolution ships
Easy · n+1 query · latency regression · versioned reads
The longitudinal record service serves a patient summary that fans out across eight to twelve resource types. After a release, the composite read's p99 went from 180ms to 2.1s while p50 moved from 40ms to 55ms. Database CPU is flat, no statement appears in the slow-query log, and the regression is worst for patients with long histories. The release added a latest-version resolution step so superseded observation_result rows stop appearing. Find the cause and give the fix, including the index it needs.
Approach
- Read the p50/p99/p99.9 split before anything else. p50 nearly flat with p99 up twelve-fold means the added work scales with a per-patient quantity — the number of resource versions on the chart — rather than a uniform slowdown. CPU saturation, lock waits and replica lag all move p50 too, so they are already excluded.
- Count database round trips per request, not query time. The signature in pg_stat_statements is one statement whose calls grew by orders of magnitude while its mean_exec_time stayed under a millisecond: a cheap query executed once per row. That is exactly why nothing crossed the slow-query threshold.
- Read the resolution code. It loops over the fetched rows and issues one query per row to test whether a newer version supersedes it, so the cost is 1 + R round trips for R versions. At 3ms of round trip each, a 600-version chart costs about 1.8s of pure latency with negligible database work.
- Replace it with one set-wise query per resource type: SELECT DISTINCT ON (accession_id, loinc_code) ... WHERE enterprise_person_id = $1 ORDER BY accession_id, loinc_code, version DESC, backed by an index on (enterprise_person_id, accession_id, loinc_code, version DESC). Round trips fall from 1 + R to the number of resource types, at most twelve, and the fan-out can run those concurrently.
- Name the trade-off against the alternatives. An anti-join on NOT EXISTS (SELECT 1 FROM observation_result o2 WHERE o2.supersedes_observation_id = o.observation_id) is correct against this schema but plans as a second pass over the person's rows. A maintained superseded marker on the predecessor row indexes best but adds a write to a table whose whole guarantee is that rows are never rewritten; DISTINCT ON keeps the append-only property.
Follow-up
- A correction commits while the summary is being assembled across eight separate queries. Which version does the caller get, and how do you make the composite read consistent — a repeatable-read snapshot, or an as-of timestamp threaded through every query?
- Would you cache the resolved latest-version set per patient? Corrections are ordinary traffic, so state the invalidation trigger and the worst-case staleness before answering yes.
Suggested practice checklist
Four days sample coding, design, fundamentals and the practical rounds at deliberately shallow depth, which is enough to surface the topics you did not know were in scope. That map, rather than a guess made on day one, decides where the last three days go.
Day 1 — Coding, one pass at shallow depth
- Solve one problem from each of six families, an array with two pointers, hash counting, binary search, a tree traversal, a graph traversal and one dynamic program, under a hard twenty-minute cap with no extensions, marking each finished, late, or stalled.
- For every stall, write the exact move you could not make rather than the subject, so the note reads could not turn the recurrence into a loop rather than bad at dynamic programming.
- Fix nothing today. The value of the pass is the unfixed record.
Deliverable: Six timed attempts marked finished, late or stalled, each stall carrying a named blocking move.
Day 2 — Design, one pass at shallow depth
- Spend twenty minutes each on three different shapes, a read-heavy feed, a write-heavy ingest path, and something needing a transaction across two entities, stopping each at requirements, interface and data model.
- After each, write the first question you could not answer, which is usually a number you could not estimate or a failure mode you had no vocabulary for.
- Mark which of the three you would be most relieved not to be asked, and treat that as data rather than as a preference.
Deliverable: Three shallow designs, each with the first unanswerable question written at the bottom.
Day 3 — Fundamentals and the practical rounds
- Answer eight short questions in writing at four minutes each, covering the material that fills the gaps between the big rounds: what happens between a URL and a rendered page, what an index costs on write, when a process is preferable to a thread, and what conditions a deadlock requires.
- Do one thirty-minute practical task of the kind a take-home compresses: read an unfamiliar two-hundred-line file and write what it does, what you would change, and the one thing you remain unsure of.
- Score every answer fluent, correct but slow, or absent, and keep the absent ones visible.
Deliverable: Eight scored short answers and one written reading of unfamiliar code.
Day 4 — The rounds that are about you, and the map
- Deliver three behavioural answers aloud against a timer, a conflict, a failure you owned, and a decision made without enough information, marking any that ran past three minutes or contained no number.
- Assemble the map: every marked item from days one to three on a single page, sorted by how likely it is to appear in your loop rather than by how uncomfortable it felt.
- Choose exactly two areas for the remaining three days and write down what you are deliberately abandoning.
Deliverable: A one-page scored map of the whole surface area with two areas chosen and the rest explicitly abandoned.
Day 5 — First chosen area, to the depth you skipped
- Work the higher-ranked area in four focused blocks, choosing items one level above where you stalled rather than repeating what already works.
- After each block write the rule you extracted in one sentence with its precondition attached, since a rule carrying no precondition is exactly what fails under a variation.
- Re-attempt the day-one or day-two item that exposed this area and compare against the original timing.
Deliverable: Four worked blocks, a timed re-attempt against the original, and three one-sentence rules with preconditions.
Day 6 — Second chosen area, where the gap is coverage rather than speed
- Treat the second area differently from the first. Day five drilled something you could already half-do; this one is usually a topic you had simply never met, so build one worked reference example end to end and keep it, rather than attempting six problems badly.
- Write down the vocabulary you were missing on day two or three, five terms at most, each with the one sentence that makes it usable in an answer rather than the textbook definition.
- Redo the shallow attempt that exposed this area and note whether you now fail later in the problem, because moving the failure point is the realistic gain from a single day and is worth more than a score that did not change.
Deliverable: One worked reference example for the newly covered area, a five-term vocabulary list, and a note on where the failure point moved.
Day 7 — Reassemble the loop
- Sit two rounds back to back with no gap, ordering them so the area you chose second comes last, because the map was built from rested, isolated attempts and the loop will reach your weaker area when you are already spent.
- Write where the second round suffered from the first, which is normally the point at which structure collapses into narration.
- Reduce the week to one page holding only the rules you can state without reading them.
Deliverable: Mock notes on cross-round carryover plus a one-page card of rules you can recite from memory.
The failure that was yours
Keep one story where the bad call was yours rather than a dependency's or a manager's. Name the check that would have caught it, whether you added that check afterwards, and whether it has fired since. Answers that route blame outward end the conversation early; answers that end in a guardrail someone still relies on tend to open it up.
Describe a situation where you had a disagreement with a team member or stakeholder and how you resolved it.
Medium · behavioural and engineering judgement
Approach
- Pick a story where you made the decision, not one where you watched it.
- State the situation in two sentences and spend the rest on the reasoning.
- Close with what you would do differently, concretely.
Follow-up
- What did you decide not to do, and why?
- What would you do differently if you ran that again?
Describe a time when you faced a major technical obstacle on a project and how you overcame it.
Medium · behavioural and engineering judgement
Approach
- State the situation in two sentences and spend the rest on the reasoning.
- Name the disagreement and how you resolved it with evidence.
- Close with what you would do differently, concretely.
Follow-up
- What did you decide not to do, and why?
- How did you know your change caused the improvement?
Unblock an engineer whose deductible accumulator overshoots
Easy · mentoring · lost update · isolation levels · reproduction
An engineer on your team has a benefit-application path that occasionally leaves a member's accumulated deductible above the plan limit — a handful of times a day, never in tests. Their code SELECTs the remaining deductible, computes patient responsibility in application code, then UPDATEs the accumulator to an absolute value. They have spent three days adding logging and are now asking whether the database is losing writes. Describe unblocking someone in this position: what you did first, what you let them find themselves, and what you left them able to diagnose next time.
Approach
- The probe is whether you teach a method or hand over a patch. Reproduce before explaining: two sessions, both BEGIN, both SELECT the accumulator row, both compute, both UPDATE to an absolute value, both COMMIT. The second overwrites the first and neither errors. Fifteen minutes of that ends three days of logging and, more importantly, ends the theory that the database is at fault.
- Be precise about where the anomaly is and is not, because the imprecise version teaches the wrong lesson. A single UPDATE that does its arithmetic in SQL is safe under READ COMMITTED: the blocked statement re-reads the row after the first commits. The loss comes from computing the new value in application code across the statement boundary, so the UPDATE writes an absolute amount derived from a stale read.
- Lay out three fixes with their costs rather than naming one. SELECT ... FOR UPDATE serialises writers per member and holds the lock for the transaction, so nothing in that transaction may make an external call. SERIALIZABLE with a retry loop on SQLSTATE 40001 requires the operation to be safely retryable. Per-member partitioning removes contention entirely and adds routing and rebalancing.
- Let them pick and defend one. The outcome you want is that they can name the anomaly and the isolation level unprompted next time, not that this particular ticket closes today.
- Leave an artefact behind: the two-session reproduction checked in, so the next person meets the interleaving rather than the symptom, and the reasoning is not stored only in your head.
Follow-up
- A claim is reversed and the accumulator must be credited back. What amount, given the plan design may have changed since?
- They choose SERIALIZABLE. How do they make the retry safe when the transaction also wrote a claim adjudication row?
- How would you have noticed this before a member saw it, and what would that check cost per adjudication?