Interview guides · Ascom
Ascom Software Engineer Interview Questions & Guide 2026
Testing practice for Ascom software engineer candidates: the boundaries worth covering, and why flakiness is a real defect.
The 60-second brief
As a Software Engineer at Ascom, you are at the intersection of mission-critical healthcare technology and sophisticated software engineering. Ascom is a global leader in healthcare information and communication technology (ICT), and your code directly impacts the efficiency of clinical workflows and the safety of patients. You will contribute to solutions that bridge the gap between medical devices and enterprise software, ensuring that vital data reaches the right caregiver at the right time.
Allocate preparation against your weakest link rather than your favourite topic. A strong algorithm habit usually comes with weak out-loud explanation of tradeoffs, and years of shipping usually come with rusty from-scratch implementation under a clock.
Focus areas
- Version clinical results instead of updating rows in place
- Design person merges that remain reversible afterwards
- Hold accumulator limits under concurrent claim adjudication
Overview
This role is both technically demanding and strategically significant. You will often work on complex, distributed systems that require high reliability and real-time performance. Whether you are developing custom controls, optimizing multi-threaded processes, or integrating disparate hospital systems, your work is fundamental to Ascom’s mission of improving healthcare delivery. You will join a collaborative environment where cross-functional teamwork with product, sales, and clinical stakeholders is essential to building products that solve real-world medical challenges.
The Ascom interview process
Ascom candidates report 5 rounds · ≈ 4-6 weeks. The stages below are what candidates describe, not a published process.
Initial Screening
The person on this call usually cannot evaluate your code and does not need to. They write a short paragraph, and that paragraph is what a hiring manager skims when deciding who to put on your loop. So the test is not whether your work was hard, it is whether a non-engineer can repeat it correctly. Name systems by what they did rather than by their internal codename, give each project a shape (what was breaking, what you changed, what happened after), and keep the whole walkthrough near ninety seconds. Depth that cannot survive a paraphrase reads as vagueness.
What to demonstrate
- Whether a non-engineer can restate your projects without distorting them, since their paraphrase is what travels to the hiring manager, not your sentences
- Whether each project has a shape rather than a stack list: the failure or constraint, the change you made, the result and how it was measured
- Whether you can say what was yours inside a team project without either inflating it or disappearing into the plural
How to prepare
- Rewrite each headline project as two sentences with no internal system names and no acronyms outside your company, then say them to someone outside engineering and have them repeat them back. Fix whatever came back wrong
- Attach one measured number to each project: the baseline, the change, and the window it was measured over. Where nothing was ever measured, say that plainly rather than reaching for a plausible percentage
- Time the background walkthrough against a clock. If it runs past two minutes, compress the earliest role to a single clause and spend the recovered time on the most recent one
HR Dialogue
When a round has no standard shape, it is often there because something is still open: an area no earlier conversation reached, a round where the signal came out mixed, or a decision someone is not ready to make alone. Work out which by going back over what each earlier round actually covered rather than how it felt, and arrive able to give evidence on that point without being asked twice. Weak answers replay the loop's earlier material at the same depth. Strong ones go a level deeper and stay consistent with what you already said.
What to demonstrate
- Whether your account of a project matches the one you gave earlier in the loop, since what you said before may be available to whoever runs this round
- Whether you can go a level deeper on something already covered, reaching the decision and its alternatives rather than repeating the summary
- Whether you state your own uncertainty accurately, including parts of a system you did not build and decisions you inherited, instead of claiming even ownership across all of it
- Whether you can answer a question you handled poorly earlier by naming what you missed, rather than delivering a polished second version as if the first had not happened
How to prepare
- Reconstruct the loop on one page: for each round, the questions you were asked and the answer you actually gave, not the better one you thought of afterwards. The gaps on that page are your best available guess at why this round exists.
- Take the two claims you made earlier that carry the most weight and assemble the backing for each: the measurement, the date, what broke, the decision you would make differently now.
- Write down the three facts about your work that must not drift between tellings, such as team size, timeline and your own role, and check your stories against that list rather than trusting recall under pressure
Hiring Manager Discussion
The design portion here is shorter and lower-stakes than a dedicated design round, which changes what it tests. There is rarely time to reach a full component diagram, so what gets read is your first two minutes: whether you pin down constraints, meaning request rate, data size, what must not be lost and how stale a read may be, before naming any technology. Opening with a stack list invites being steered back. Once the numbers are on the table, say what breaks first if they grow tenfold, and defend the plain option where the load does not justify more.
What to demonstrate
- Whether constraints come before components: peak request rate, data volume, what must survive a process dying, and the staleness the product can tolerate
- Whether you can name what saturates first when traffic grows by an order of magnitude, and whether that matches the design you just sketched
- Whether a cache is reasoned about on both paths, since a cold or recently flushed cache sends the full request rate to the origin, so capacity has to cover the miss case and not only the steady state
- Whether you distinguish what you have operated from what you have only read about, which usually shows up in the answer to why a particular component is there
How to prepare
- Take one system you worked on and write down the numbers you would need to defend it: requests per second at peak, rows in the largest table, the latency you were actually held to. Not having them is the common stall in this part.
- Practise the tenfold question on that system out loud, naming the first bottleneck you would hit, whether that is a single writer, connection limits, disk, or a queue that grows faster than it drains, and the smallest change that buys headroom.
- Prepare one decision where the plain option was correct: the cache you did not add or the queue you did not introduce, with the load figure that made that the right call. Being able to argue for less is rarer than being able to argue for more.
Technical Evaluations
Most of the time lost in this format is not lost to thinking. It goes to a standard-library call you half-remember, an off-by-one in a loop bound, and a debugging loop that mutates code at random until something passes. When output is wrong, stop re-reading the whole function: take the smallest input that reproduces it and walk the state through by hand, printing intermediates if the environment allows. Guessing at a fix without a failing case you understand is how a five-minute bug becomes twenty, and the clock does not pause while you do it.
What to demonstrate
- Whether you reach the right structure without a detour, and can write it from memory rather than only recall that one exists
- Whether overflow is considered where the language has fixed-width integers, since a signed 32-bit value stops at 2,147,483,647 and then wraps in Java, is undefined behaviour in C++, and does not arise in Python, whose integers grow instead
- Whether recursion depth is treated as a constraint on large inputs, given that CPython's default limit is 1000 frames and a deep recursion can exhaust the stack in any language where an iterative version would not
- Whether a failing case is isolated and explained before any edit is made to the code
How to prepare
- From an empty file and with no references open, implement the pieces you lean on most: a heap push and pop, an iterative DFS with an explicit stack, and a binary search whose midpoint is written lo + (hi - lo) / 2, which avoids the overflow that (lo + hi) / 2 can hit in a fixed-width integer type
- Time yourself on the ten library calls you look up most, such as sorting with a custom comparator, splitting and joining strings, and finding the next key at or above a value in an ordered map, until the lookup is gone
- Take a solution you know is broken and, before touching it, write one sentence naming the input, the expected value and the actual value. Repeat until you do it without deciding to.
Final Discussions
Where the day includes a partner from product, design or data, that conversation is weighted like the technical ones and prepared for least. They are deciding one thing: whether having you in the room makes their decisions cheaper. That means options with costs attached, not implementation detail and not "it depends". An estimate someone can plan against — a range, the assumption that would push it to the high end, and what you would drop to hit the low one — is worth more than a confident single number, which everyone present already knows is wrong.
What to demonstrate
- Whether an estimate comes as a range with the assumption most likely to break it, and states what a specific scope cut would actually buy
- Whether a technical constraint is handed over as a choice with consequences on their side, rather than as a verdict they have no standing to argue with
- Whether you establish what decision is on the table before proposing anything
- Whether risk is raised while it can still change the plan, with the trigger that would confirm it, instead of reported afterwards as a slip
How to prepare
- Take a project that shipped late and write the two-sentence warning you could have given three weeks earlier, naming what you would have needed decided at that point
- Rehearse one estimate out loud until it arrives in three parts: the range, the single assumption that would blow it, and the smallest thing you would cut to protect the date
- Rewrite an objection you have actually made — the "we can't do that" version — as two options with their costs, so the choice ends up with the person who owns it
Where candidates lose points
Assuming admission, discharge and transfer messages arrive in the order the events happened.
Interface engines route by message type across separate queues and retry independently, so a discharge can land before the admission it closes and an update can land before the registration it modifies. Ordering has to come from the sender's event timestamp plus a per-encounter sequence, and the consumer has to apply out-of-order and late-arriving events correctly rather than rejecting them, because rejection turns a recoverable ordering issue into permanent data loss that nobody notices until a report is short.
Writing the access audit record inside the read transaction, or firing it off after the response with no durability.
Inside the transaction, an audit-store outage blocks clinical reads and turns a logging dependency into a care outage. Fire-and-forget afterwards means the audit trail is incomplete during precisely the incidents it exists to reconstruct, and the gaps are invisible until someone asks for the log. The usual resolution is committing the access decision and its audit row together to a local outbox and shipping asynchronously, which keeps the read path available while preserving durability.
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.
Sorting when the problem never required a total order
Match the algorithm to the guarantee actually needed: the top k comes from a size-k heap in O(n log k) time and O(k) space, distinctness needs a set rather than an ordering, and a small bounded integer key range admits a linear counting pass. A full O(n log n) sort is the right default only when you genuinely need everything in order.
Questions & practice
Flatten overlapping coverage spans into a primary-payer timeline
Medium · sweep line · intervals · bitemporal
For one enterprise_person_id you hold up to 10,000 coverage_span rows: coverage_id, payer_id, plan_id, effective_date, termination_date (null means open-ended), coverage_order (1 is primary), status, valid_from and valid_to (system time). Given a system instant T and a date range D0 to D1, return disjoint business-date intervals covering that range, each labelled with the active coverage_id of lowest coverage_order, and with uncovered stretches returned explicitly. termination_date is the last covered day. Only status active counts. Target O(N log N) time, O(N) space.
Approach
- Apply the system-time slice before any interval logic: keep rows where valid_from is at or before T and valid_to is after T. That single predicate is what makes the answer 'what we believed at T' rather than 'what we believe now', and a retroactive termination loaded after T leaks straight into the output if you skip it.
- Convert each surviving row to a half-open day interval from effective_date up to termination_date plus one day, using positive infinity for a null termination. Half-open removes every off-by-one at the join between a termination and the next plan's effective date, which is where same-day switches get turned into false gaps.
- Sweep: emit 2N boundary events, sort by date in O(N log N), and hold the active set in a min-heap keyed by (coverage_order, coverage_id) with lazy deletion, popping the top while it has already expired at the current boundary. Each coverage is pushed once and popped once, so the sweep is O(N log N) total and O(N) space.
- Emit an output interval whenever the heap top changes between consecutive boundaries, and emit an explicit uncovered interval when the heap empties. A gap reported as a gap is a different answer from a gap omitted, and downstream the difference is a denial versus a silent assumption of coverage.
- Fix the tie-break and state it: lowest coverage_order, then lowest coverage_id. Two rows at order 1 is a data error, and without a deterministic rule the coordination-of-benefits answer changes between two runs over identical data.
- Clip to D0 and D1 last, so a coverage that starts before D0 still contributes its correct order inside the window.
Worked solution (30 min)
- Filter to rows live at T, then map each to a half-open interval and a (coverage_order, coverage_id) priority.
- Build the boundary list, sort it, and sweep with a lazily-deleted min-heap.
- At each boundary, drop expired heap tops and compare the new top to the previous one, emitting an interval on change.
- Emit an uncovered interval whenever the heap is empty between two boundaries.
- Clip the emitted list to D0 through D1 and assert the intervals are disjoint and contiguous.
Expected result: Window 2026-01-01 to 2026-12-31 with three live rows: coverage 1 at order 1 effective 01-01 terminating 06-30, coverage 2 at order 2 effective 03-01 open-ended, coverage 3 at order 1 effective 09-01 open-ended. Output is three intervals: 01-01 to 06-30 labelled coverage 1, 07-01 to 08-31 labelled coverage 2, 09-01 to 12-31 labelled coverage 3, with no gaps. Delete coverage 2 and the middle interval becomes an explicit uncovered stretch of 07-01 to 08-31.
Verify it yourself
- The emitted intervals are sorted, pairwise disjoint, and their union is exactly D0 through D1 once uncovered stretches are included.
- Every boundary date in the output is D0, the day after D1, some effective_date, or some termination_date plus one day. Any other date means a boundary was synthesised.
- Removing the lowest-order coverage promotes exactly the next order and leaves every boundary it did not touch unchanged.
- Choosing T before the load time of a retroactive termination returns the pre-correction timeline, and T after it returns the corrected one, from the same table.
Follow-up
- Run the same query at two system instants and diff the timelines. What did the correction change, and what does each extra instant cost you?
- A termination arrives with an effective date earlier than the service date of a claim that already adjudicated and paid. What does the timeline now say, and what has to happen to that claim?
- Two enrolment files disagree on coverage_order for a dependent. What does your tie-break do, and what should the system do instead of tie-breaking?
Resolve enterprise identity from a link log that supports unmerge
Hard · union-find · rollback · identity resolution
person_identity_link holds link_id, enterprise_person_id, assigning_authority, source_person_id, match_score, link_status (auto_linked, manual_linked, potential_duplicate, unlinked, rejected), version, superseded_by_link_id, decided_by, decided_at. You have up to 40 million source identities and 60 million decisions applied in decided_at order. Build a structure answering which enterprise identity a given (assigning_authority, source_person_id) resolves to after any prefix of the log, and supporting a revert of the k most recent merges at O(1) each. State the query complexity, and say why path compression is unavailable to you.
Approach
- Intern the node key as the pair (assigning_authority, source_person_id) into a dense integer index. Never key on source_person_id alone: two facilities in one network routinely issue the same medical record number to different people, so a bare-value key merges two patients before any matching logic has run, and a single-source test fixture will never show it.
- Classify the log rows before touching the structure. auto_linked and manual_linked are unions; potential_duplicate and rejected are recorded decisions that must not union anything; unlinked is a revert of the link it supersedes, not a new edge.
- Use union by size with an explicit undo stack, pushing (attached_root, its previous parent, the previous size of the absorbing root) on every union. Find walks parent pointers to the root: union by size bounds tree height at log2(n), so a query is O(log n) worst case, a union is O(log n), and a revert pops two words and is O(1).
- Path compression is the thing you have to give up, and the reason is specific: it rewrites parent pointers of nodes that were never named in the union being recorded, so the undo entry no longer describes the mutation that happened and a revert restores a forest that is quietly wrong. Near-constant find is only available to an append-only structure.
- Hold enterprise_person_id as a property of the root slot rather than a value copied onto every member. A merge then relabels one slot instead of n rows, and an unmerge restores two labels instead of reconstructing n.
- State the price plainly: every read path gains a resolution hop and no downstream table can carry a plain patient_id column. That cost is what buys reversibility, and it belongs in the design discussion rather than being discovered later by whoever writes the first join.
Follow-up
- A merge is found wrong three weeks and 200,000 unions later, and your stack only reverts a suffix. What do you do instead, and what does that cost?
- A claim was posted under the losing identity before the merge. After the unmerge, which identity owns it, and what in your design let you answer that?
- Two matcher instances submit unions concurrently. What is the smallest change that keeps both the structure and the undo stack consistent?
Reconstruct what the chart displayed at five million past instants
Hard · external sort · as-of join · io bound
observation_result holds 200 million versions: observation_id, enterprise_person_id, filler_order_id, loinc_code, value_numeric, result_status, collected_ts, issued_ts, version, supersedes_observation_id. An incident review hands you 5 million queries of (enterprise_person_id, filler_order_id, loinc_code, as_of_instant). For each, return the version that was visible at that instant, meaning the one with the greatest issued_ts at or before it. Neither side fits in memory. The per-query scan is correct. Explain precisely why it is too slow, then give a plan with its complexity.
Approach
- Name the clock before naming an algorithm. Visibility is issued_ts, the release time. collected_ts is when the specimen was drawn and can precede release by hours, so ordering on it reports a correction as visible long before anyone could have seen it. No amount of index work rescues the wrong column.
- Be exact about why the naive plan fails, because the interviewer is testing whether you can tell arithmetic cost from I/O cost. Per query the chain scan is O(V_k) and the arithmetic is trivial, but 5 million independent lookups into a 200-million-row structure that does not fit in RAM is 5 million random reads. The job is bounded by seeks per query, not by comparisons, and buying a faster comparison changes nothing.
- Convert random access into sequential access. Hash-partition both sides on the chain key (enterprise_person_id, filler_order_id, loinc_code) into P shards sized to fit memory, sort each shard's versions by (chain key, issued_ts) and its queries by (chain key, as_of_instant), and sweep the pair in lockstep. Total O((V + Q) log(V + Q)) with external sort, replacing Q seeks with two sequential passes.
- Inside a chain the sweep is linear, not logarithmic, because queries are visited in ascending as_of order and the version pointer only moves forward: O(V_k + Q_k) per chain. Binary search per query is the better shape only when Q is small relative to V and the versions are already indexed and resident.
- Return the empty answer as a distinct outcome. A query whose as_of precedes the first issued_ts means nothing was displayed, which is not the same as the earliest value, and is frequently the exact fact the review is chasing.
- Return a version later marked entered_in_error if it was live at the instant asked about. Reconstructing the past means reporting what was on the screen, including what was wrong, and quietly substituting today's truth defeats the purpose of the exercise.
Follow-up
- Read replicas lagged 40 seconds at the time. Does your answer describe what the clinician actually saw, and how would you bound the difference?
- Serve the same question online for a single chart at a p99 under 50ms. What changes?
- One partner changed its filler_order_id format mid-year, so the chain key is not stable. How does that appear in your output, and how do you detect it rather than returning empty answers?
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?
Namespace source identifiers so a cross-facility join cannot collide
Easy · identity · composite keys · constraints
person_identity_link holds one row per (assigning_authority, source_person_id) per link version: link_id, enterprise_person_id, assigning_authority, source_person_id, match_score, link_status in ('auto_linked','manual_linked','potential_duplicate','unlinked','rejected'), version, superseded_by_link_id, decided_by, decided_at. Two facilities in one network both issued medical record number 004821, to different people. An existing extract joins on source_person_id alone and has been merging their charts. Give the constraint set that makes the bare join impossible to write by accident, then write the query that resolves one facility's (authority, MRN) to its current enterprise_person_id.
Approach
- Name the root cause precisely: an MRN is unique only inside the authority that issued it, and a member ID only inside payer plus plan. The identifier is half a key; the namespace is the other half.
- Make the composite the only addressable key. UNIQUE (assigning_authority, source_person_id, version) gives version history; a partial unique index on (assigning_authority, source_person_id) WHERE superseded_by_link_id IS NULL enforces one live decision per source record.
- Remove the bare column as a join target from every consuming view: expose a view or function that takes both arguments, so a one-argument lookup is a compile-time error rather than a silent cross-patient merge.
- Write the resolution query against the live version only, and filter link_status separately — 'unlinked' and 'rejected' are live rows that mean 'no enterprise identity', so they must not be confused with 'no row'.
- State the detection query you would run before trusting any existing extract: group source_person_id across authorities and count distinct authorities, which surfaces every collision already in the data.
Worked solution (20 min)
- Write the two constraints and say which invariant each one holds: the three-column UNIQUE holds version history, the partial unique index holds single-live-decision.
- Write the resolution query filtering on superseded_by_link_id IS NULL plus the two linked statuses.
- Write the collision detector over the existing table and run it mentally against the 004821 case to confirm it returns that value with two authorities.
- Decide the contract for zero rows versus a live 'unlinked' row: zero rows means the source record was never seen, 'unlinked' means it was seen and deliberately not attached, and the caller must handle them differently.
- Write the one-line fix for the broken extract: add the authority column to the extract and to the join, then re-run and diff the row count against the old output.
Expected result: Constraints: UNIQUE (assigning_authority, source_person_id, version) and CREATE UNIQUE INDEX ON person_identity_link (assigning_authority, source_person_id) WHERE superseded_by_link_id IS NULL. Resolution: SELECT l.enterprise_person_id FROM person_identity_link l WHERE l.assigning_authority = :aa AND l.source_person_id = :mrn AND l.superseded_by_link_id IS NULL AND l.link_status IN ('auto_linked','manual_linked'); zero or one row by construction. Detector: SELECT source_person_id, count(DISTINCT assigning_authority) AS authorities, array_agg(DISTINCT assigning_authority) FROM person_identity_link GROUP BY 1 HAVING count(DISTINCT assigning_authority) > 1;
Verify it yourself
- The detector returns 004821 with two authorities, confirming the collision is in the data and not a theory.
- Inserting a second live row for one (authority, MRN) raises a unique violation, while inserting a superseded row for the same pair succeeds.
- The resolution query returns at most one row for every (authority, MRN) pair in the table — run it as a LATERAL over the distinct pairs and assert no count exceeds one.
- Re-running the repaired extract yields a strictly different enterprise_person_id for at least one of the two 004821 patients.
Follow-up
- The spine stores enterprise_person_id directly on encounter and claim_line. What does a wrong merge cost you with that design, and what would you store instead to keep unmerge a supported operation?
- A link is downgraded from auto_linked to potential_duplicate. Should the resolution query return the old enterprise identity, nothing, or an error, and who decides?
- Your partial unique index blocks a second live row. How does the merge path insert the new version and supersede the old one without violating it mid-transaction?
Describe custom controls you have created.
Medium
Approach
- Clarify what is being asked and what a complete answer contains.
- Say what you would check first and why it is the highest-information step.
- Work from the requirement backwards to the design.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
What are your specific programming foundations and technical interests?
Medium
Approach
- State your assumptions explicitly before working the problem.
- Clarify what is being asked and what a complete answer contains.
- Say what you would check first and why it is the highest-information step.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
Idempotent admission feed ingestion under replay and reordering
Easy · idempotency · at-least-once · event-ordering · queues
The gateway consumes HL7v2 admit, discharge, transfer and update messages over MLLP: a few million a day with fourfold nightly bursts, across twelve parallel workers. Delivery is at-least-once — a lost acknowledgement makes the sender resend, and an interface restart replays a window. Message types are routed through separate queues and retried independently, so a discharge can arrive before the admission it closes. Define the idempotency key and the assumption it rests on, the mechanism that makes deduplication atomic under concurrency, and how the encounter row reaches the same final state under any arrival order.
Approach
- Reject the message control identifier on its own: it is unique only per sending application, and senders roll the counter over. Use the tuple of sending facility, sending application, control identifier and the message timestamp, or a SHA-256 of the raw message body when the header cannot be trusted to be stable. Write the assumption down — no control identifier reuse within the timestamp's resolution — and count near-duplicate bodies with distinct keys so you learn in production when it stops holding.
- Make deduplication a write-side decision: insert the key into a ledger under a UNIQUE constraint in the same transaction that applies the effect. A select-then-insert in application code is not sufficient, because two workers holding the same replayed message both pass the existence check before either inserts; the unique violation is the only thing that serialises them. Deduplicating on read costs you the effect already having been applied.
- Order per encounter rather than per queue. Carry the sender's event timestamp and apply with UPDATE ... WHERE source_event_ts < :incoming plus a row_version bump, so an older message loses deterministically regardless of which worker wins the race.
- Make out-of-order arrival constructive instead of a rejection: a discharge for an unknown encounter inserts the encounter in status discharged with the discharge fields set, and the later admission fills only what is missing. Rejecting the orphan turns a recoverable ordering problem into permanent loss that surfaces months later as a census report that is short.
- Prove the property rather than assert it: replay a fixture stream containing a duplicate, an early discharge and a late correction under several random permutations and assert the final encounter rows are identical. If the final state depends on the permutation, the merge rule is wrong and the fixture is fine.
Worked solution (25 min)
- Write the idempotency key as a concrete tuple, then list each field's uniqueness scope and which sender behaviour would break it.
- Write the ledger insert and the effect apply as one transaction, and state what the handler returns on unique violation.
- Write the conditional UPDATE that enforces per-encounter ordering, and show what happens when a late update loses.
- Define the merge rule for a discharge preceding its admission, naming which fields each message may set and which it must not overwrite.
- Permute a ten-message fixture five ways and compare the final rows field by field.
Expected result: A composite idempotency key with its assumption stated and monitored, deduplication enforced by a UNIQUE constraint inside the effect transaction rather than by an application-level existence check, per-encounter ordering driven by the sender's event timestamp, and an upsert-and-fill merge rule that makes the final state independent of arrival order.
Verify it yourself
- Replaying the same message ten times produces one encounter row and one ledger entry.
- Five random permutations of the same fixture stream yield identical final rows.
- Two workers processing the identical replayed message simultaneously produce exactly one effect, with the loser observing a unique violation.
- A discharge arriving before its admission leaves the encounter discharged rather than rejected or re-opened.
Follow-up
- A sender re-emits an entire day after a restart with the same control identifiers but fresh message timestamps. What does your key do, and what would you change?
- Two update messages for one encounter carry identical sender event timestamps. How do you break the tie deterministically across twelve workers?
Evening eligibility denials cluster in western time zones
Easy · timezones · bitemporal data · date boundaries
Eligibility denials rose from 0.4% to 3.1% of responses. They cluster after 17:00 local time, only at facilities in UTC-5 through UTC-10, and spike on the first and last day of each month. coverage_span.effective_date and termination_date are date columns in business time; the service derives the service date from encounter.admit_ts, a timestamptz. The enrolment files for the affected members look correct on inspection. Give the ordered diagnosis and the fix.
Approach
- Pivot the denial rate two ways before reading code: by facility UTC offset and by local hour of day. A defect that tracks local hour and offset is a time-conversion bug; one that tracks a source file, payer or plan is a data bug. The month-boundary spike is a consequence rather than a second problem, because terminations cluster on month ends and a one-day skew is most visible there.
- Take one denied request and recompute it by hand: the raw admit_ts, its UTC calendar date, the facility-local calendar date under the facility's IANA zone, and the coverage row's effective_date and termination_date. If the answer flips between the UTC date and the local date, the conversion is the cause and no further hypotheses are needed.
- Find the cast. In PostgreSQL, timestamptz::date is evaluated in the session's TimeZone setting, so the identical SQL returns different dates on different pooled connections and a pool that inherits UTC yields the UTC calendar date. date columns carry no zone at all, so comparing a date column to a UTC-derived date compares two different calendars.
- Fix once, at the edge: resolve the service date as (admit_ts AT TIME ZONE facility.iana_zone)::date and pass it down as an explicit date parameter. Use the IANA zone name, never a stored numeric offset, because the offset changes twice a year under daylight saving and a stored -8 is wrong for roughly eight months.
- Forbid re-derivation below the edge and pin the boundary with a frozen-clock test: an encounter at 23:30 local at a UTC-10 facility on the last day of a month whose coverage terminates that day, plus the same encounter one minute later. A test that calls now() passes for most of the day and hides the defect.
Follow-up
- Where else does the as-of date enter the system — the eligibility cache key, claim_line.service_from_date, the accumulator's plan year? Which of those are already computed in UTC?
- An inpatient encounter spans midnight local. Which date governs eligibility — the admit date or the service line date — and at which layer is that decided?
Suggested practice checklist
Roughly ninety minutes on weeknights with one longer weekend block. The plan cuts scope rather than compressing everything, on the assumption that one thing finished per night beats four half-started.
Day 1 — Fix the scope and take a cold baseline
- Read the role description and write the three things the loop will almost certainly test, then write an explicit not-doing list and keep it visible all week.
- Take one twenty-five-minute coding problem and one fifteen-minute design prompt cold, and write the single sentence naming what blocked each, because those two sentences decide where the remaining evenings go.
- Set the week's rule: one thing finished every night, including the night you only have forty minutes.
Deliverable: A one-page scope with a not-doing list and two cold attempts, each carrying one sentence on what blocked it.
Day 2 — One pattern, written three times from blank
- Choose the single pattern most likely to appear in your loop and write it three times from an empty file rather than editing the previous attempt.
- On the third pass, write the invariant as a comment before the loop body and the complexity before the first line of code.
- Stop at ninety minutes even if the third version is imperfect, and write the one thing you would fix given another hour.
Deliverable: Three independent implementations of the same pattern plus a note on what changed between them.
Day 3 — One design, only to the depth you can defend
- Take one system shape and go only as far as requirements, interface and data model, refusing to draw a box you could not survive a follow-up about.
- Attach one number to each non-functional requirement, deriving it rather than asserting it, and write the assumption the number rests on.
- Write the one tradeoff you are choosing against and the observation that would make you reverse it.
Deliverable: One design at interface-and-schema depth with derived numbers and one written reversible tradeoff.
Day 4 — Only the fundamentals you will have to defend
- Write, in under two hundred words each, the answers to the two questions that follow almost any implementation: why this structure and not the obvious alternative, and what happens to this code at a hundred times the input.
- Write what an index actually costs: faster lookups on the indexed columns against a write that now maintains a second structure, plus the cases where the planner declines to use it anyway, low selectivity, or a predicate wrapping the column in a function.
- Delete any answer you cannot deliver aloud in under a minute, since an answer that needs reading is not an answer you have.
Deliverable: Three written answers, each under two hundred words and each timed aloud.
Day 5 — Your own work, timed
- Write a ninety-second and a four-minute version of your main project and time both aloud rather than reading them.
- Prepare the two follow-ups that always come: what you would do differently, and how you knew it worked.
- Put one number in the first sentence and be ready to say exactly where it came from and what it excludes.
Deliverable: Two timed narratives with one defensible number in the opening line.
Day 6 — The one full rehearsal, in the weekend block
- Run a sixty-minute mock covering a coding round and a design round in one sitting with no break, because sustained attention is the thing evenings have not trained.
- Immediately afterwards, and before hearing any feedback, write the three moments you lost the thread.
- Spend the rest of the block only on those three moments, and on nothing you merely feel shaky about.
Deliverable: Mock notes naming three failure moments with a specific fix written under each.
Day 7 — Taper
- Write the twenty-minute warm-up you will actually do on the morning: one problem you can already solve from a blank file, one design you can narrate, and nothing you have never seen.
- Re-read only your own notes from this week and open no new material.
- Write the logistics down: the editor or shared document you will be working in, whether execution and lookups are permitted, and the sentence you will use when you do not know something.
Deliverable: A one-page card holding the design structure, the project numbers, and the logistics.
Outage answers are graded on instruments
Nobody is scoring your stamina at three in the morning. What carries weight is which signal told you something was wrong, what you measured before touching anything, what you rolled back versus what you fixed forward, and why you picked one. 'We restarted it and it went away' is a story about not knowing.
Give us an example of a work-related challenge you faced and how you handled it.
Medium · behavioural and engineering judgement
Approach
- State the situation in two sentences and spend the rest on the reasoning.
- Close with what you would do differently, concretely.
- Name the disagreement and how you resolved it with evidence.
Follow-up
- What did you decide not to do, and why?
- What would you do differently if you ran that again?
What is your experience with multi-threading?
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
- How did you know your change caused the improvement?
- What would you do differently if you ran that again?
Can you detail your experience with technologies previously used in your career?
Medium · behavioural and engineering judgement
Approach
- Name the disagreement and how you resolved it with evidence.
- Close with what you would do differently, concretely.
- Pick a story where you made the decision, not one where you watched it.
Follow-up
- How did you know your change caused the improvement?
- What would you do differently if you ran that again?
What are your professional strengths and weaknesses?
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.
- 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?