cswork / libraryOpen practice workspace

Interview guides · Imperative Care

Imperative Care Software Engineer Interview Questions & Guide 2026

Imperative Care software engineer prep on databases: indexes, query plans, and the anomalies an isolation level still permits.

The 60-second brief

As a Software Engineer at Imperative Care, you play a vital role in developing novel, robotic-assisted technologies and interventional capabilities designed to forever change the outcomes of ischemic stroke. This life-altering disease impacts close to a million people a year in the U.S. and 10 million worldwide, making your contributions directly tied to bringing advanced treatment to populations currently without access. You will build and scale the infrastructure, applications, and automation tools that power cutting-edge medical devices, directly influencing patient care and clinical success.

Scope in the operational half of the job when the seat carries on-call. Rollout, rollback and what you would put on a dashboard belong inside a design answer rather than after it, and an answer that never reaches them reads as someone who has built systems but not run them.

Focus areas

  • Make HL7 and X12 ingestion idempotent under replay
  • Design person merges that remain reversible afterwards
  • Authorise every identified read against current consent

Overview

The work environment at Imperative Care is fast-paced, highly collaborative, and technically demanding. You will bridge complex technical domains—often overlapping software, mechanical, and electrical engineering—to deliver robust, reliable, and scalable software solutions. Whether you are developing automated testing infrastructure, simulation tools, or user-facing applications, your code must adhere to rigorous medical device standards while operating seamlessly within a modern robotics platform.

Expect to work alongside cross-functional teams comprising clinical experts, controls engineers, hardware specialists, and systems architects. While the technical scope is broad and challenging, the impact of your daily work is profound. You are stepping into an organization that values innovation and rigorous engineering, where your solutions actively push the boundaries of modern medical robotics.

The Imperative Care interview process

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

Initial Phone Screen

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 Assessments

Input bounds are the part of the prompt most often skimmed, and they usually contain the answer. They tell you which complexity class is admissible, which narrows the search before you have thought about the problem itself. As a rough planning figure, a compiled language does on the order of 10^8 simple operations per second and an interpreted one roughly an order of magnitude less. So n up to about twenty admits enumerating subsets, a few thousand admits a quadratic pass, and a million admits neither: you need near-linear, or linear with a log factor. If the bounds are missing, ask for them.

What to demonstrate

  • Whether the approach is justified by the stated input size rather than by whichever pattern you recognised first
  • Whether you ask about the properties that change the algorithm: whether the input arrives sorted, whether duplicates occur, whether values are bounded integers, whether it all fits in memory
  • Whether you can name the bottleneck in your own solution and what would remove it, even when you deliberately leave it in place
  • Whether a claimed speedup is real, since memoising a recursion only helps when subproblems genuinely overlap and the state can be keyed cheaply

How to prepare

  • For each algorithm you rely on, write down the largest n it handles in roughly a second, then check two of those figures by timing them in the language you will actually type in
  • For two weeks, write one line naming your target complexity and the bound that justifies it before you write any code, then compare that line with what you ended up submitting
  • Practise the conversion backwards: given a required O(n log n), list the mechanisms that get you there (sorting, a heap, an ordered map, divide and conquer) and choose by what the problem needs to query, not by what you used last

Behavioral Interviews

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

Final Interviews

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

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.

Storing monetary amounts as floating-point numbers.

Binary floating point cannot represent values like 0.10 exactly, so summing allowed and paid amounts across millions of claim lines accumulates representation and rounding error. Reconciliation against a remittance file is exact to the cent, so the drift surfaces as a balance that is off by a few cents with no identifiable cause, and engineers lose days looking for a logic bug in code that is correct apart from its numeric type. Integer minor units or an exact decimal type removes the class entirely.

Comparing floating-point values for equality, or holding money in them

Binary floating point cannot represent 0.1 exactly, so repeated addition drifts and an equality check fails on values that are mathematically equal. Store currency as integer minor units or a decimal type, and compare floats against a tolerance you chose for a stated reason.

Naming no test cases at all

State what you would test before being asked: empty input, a single element, all elements equal, the maximum permitted size, and the input that exercises the branch you just wrote. It costs thirty seconds and is much of what separates someone who has shipped code from someone who has only solved puzzles.

Questions & practice

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

  1. 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.
  2. 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.
  3. 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).
  4. 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.
  5. 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.
  6. 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.

Worked solution (40 min)

  1. Intern three nodes: A is (AUTH1, MRN-77), B is (AUTH2, MRN-77), C is (AUTH1, MRN-90).
  2. Apply union(A, C) from a manual_linked row, pushing its undo entry.
  3. Apply union(A, B) from an auto_linked row with a high match score, pushing its undo entry.
  4. Query all three, then pop one undo entry and query all three again.
  5. Assert the parent and size arrays element-wise against a snapshot taken before the second union.

Expected result: After the first union, A and C share a root and B is its own root. After the second, all three share a root. After reverting one union, B is its own root again and A and C remain merged, with the first union's record untouched. Note what the key bought you: A and B collide on MRN-77 and would have been one identity from the first insert under a bare-value key, with no decision row ever recorded.

Verify it yourself

  • After reverting all k unions, the parent and size arrays are identical element by element to the pre-union snapshot, not merely equal in their query answers.
  • Tree height never exceeds log2(n). Assert it, because one union that attaches by arbitrary root instead of by size breaks the find bound without any visible symptom.
  • Applying a potential_duplicate or rejected row changes no query answer and pushes nothing onto the undo stack.
  • Querying (AUTH1, MRN-77) and (AUTH2, MRN-77) before any link row returns two distinct identities.

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?

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.

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?

Flag a requester reading too many distinct charts per window

Medium · sliding window · two pointers · distinct count

You consume access-audit records (event_ts, requester_id, enterprise_person_id, purpose_of_use, break_glass) at tens of thousands per second, non-decreasing in event_ts. For each requester, emit an alert the first time any sliding 600-second window contains reads of more than D distinct enterprise_person_ids. Break-glass reads count toward the window and are also reported separately. Return (requester_id, window_start_ts, distinct_count). Target O(1) amortised per event and memory proportional to the events resident in the window, not to the day.

Approach

  1. Per requester, hold a deque of (event_ts, person_id) and a hash map from person_id to its occurrence count inside the window, plus a running distinct counter. Push on the right; while the front is older than event_ts minus 600 seconds, pop it, decrement its count and erase the key when the count reaches zero, decrementing the distinct counter. Each event is pushed once and popped once, so the amortised cost is O(1) and the memory is O(W) for window occupancy W.
  2. Test the threshold immediately after each push and nowhere else. Between two consecutive events the window can only lose members as its left edge advances, so the maximum distinct count over all window positions is attained at a position whose right edge is an event. Checking at pushes is therefore exhaustive rather than a sampling approximation.
  3. Latch the alert per requester and re-arm only when the distinct count falls back below D, otherwise one busy stretch emits thousands of near-identical rows and the real signal is buried by its own volume.
  4. Bound memory both per requester and globally. A requester whose window legitimately holds tens of thousands of reads must not hold the process hostage, so cap the deque and degrade above the cap to an approximate distinct counter such as HyperLogLog, stating the error you accept in exchange.
  5. Count break-glass toward the window but carry it in its own output field. Break-glass has to succeed during an emergency, which is exactly why it must be the most visible path in the audit, and excluding it from the count would make the abuse route the quiet one.
  6. State the precondition: this is correct only while input is non-decreasing in event_ts. Out-of-order arrival needs a bounded-lateness buffer and a watermark, and dropping late events without a counter is the failure that hides itself.

Follow-up

  • One region's events arrive up to 90 seconds late. What buffer do you add, and what does that do to alert latency?
  • One person uses two requester accounts. What has to change in the key, and what new false positive does that introduce?
  • How do you restore window state after a process restart without replaying the whole day?

Diagnose why an eligibility lookup ignores its index

Medium · indexing · query planning · partial indexes

A 40-million-row coverage_span serves eligibility lookups with: SELECT coverage_id, plan_id, coverage_order FROM coverage_span WHERE enterprise_person_id = $1 AND valid_to = 'infinity' AND effective_date <= $2 AND (termination_date IS NULL OR termination_date >= $2). An index exists on (effective_date). EXPLAIN (ANALYZE, BUFFERS) shows a sequential scan reading 1.8 million buffers at 2.4 seconds. Explain why that index cannot help, propose the index plus any schema change that makes the predicate index-friendly, and state what you expect the new plan and buffer count to be.

Approach

  1. Read the predicate for selectivity before touching the index. effective_date <= $2 matches most of a 40-million-row history, so a leading range scan on that column returns a large fraction of the table and the planner correctly prefers a sequential scan over millions of random heap fetches. The index is not being ignored; it is being rejected on cost.
  2. Put the equality column first. enterprise_person_id = $1 selects a handful of rows out of 40 million, so it must lead the composite index; a b-tree can only use columns after the first range predicate as filters, not as search bounds.
  3. Turn the open-ended termination into something indexable. The OR ... IS NULL branch forces either a BitmapOr or a post-index filter. Add coverage_end date GENERATED ALWAYS AS (COALESCE(termination_date, DATE '9999-12-31')) STORED and rewrite the predicate to coverage_end >= $2; both COALESCE over a column and a date literal are immutable, so the generated column is legal.
  4. Make the index partial on WHERE valid_to = 'infinity'. Most rows in a bitemporal table are superseded beliefs, so the partial index is a fraction of the full one, stays in cache, and encodes the predicate for free rather than re-checking it per row.
  5. Consider the range alternative honestly: a daterange(effective_date, termination_date + 1, '[)') column with a GiST index and the && operator handles overlap queries and supports an EXCLUDE constraint against overlapping active coverage, but GiST lookups are slower than b-tree for this point-in-time pattern. Pick b-tree for the read path and keep GiST only if you also need the overlap constraint.
  6. Verify rather than assert: re-run EXPLAIN (ANALYZE, BUFFERS) and read Rows Removed by Filter and the shared hit/read split, not just the total time.

Worked solution (30 min)

  1. Run EXPLAIN (ANALYZE, BUFFERS) on the original and write down estimated versus actual rows for each predicate, which shows effective_date <= $2 is not selective.
  2. Add the generated column and rewrite the predicate to use coverage_end.
  3. Create the partial composite index and ANALYZE the table so the planner has statistics for the new column.
  4. Re-run EXPLAIN (ANALYZE, BUFFERS) and confirm an index scan with Rows Removed by Filter at or near zero.
  5. Measure the partial index size against the full-table equivalent with pg_relation_size to confirm the cache argument is real and not assumed.

Expected result: The (effective_date) index is rejected because effective_date <= $2 is a low-selectivity leading range predicate over 40M rows; the planner's estimated cost of random heap access exceeds a sequential scan. Fix: ALTER TABLE coverage_span ADD COLUMN coverage_end date GENERATED ALWAYS AS (COALESCE(termination_date, DATE '9999-12-31')) STORED; CREATE INDEX CONCURRENTLY idx_coverage_active ON coverage_span (enterprise_person_id, effective_date, coverage_end) WHERE valid_to = 'infinity'; and rewrite the last predicate as coverage_end >= $2. The new plan is an Index Scan on idx_coverage_active, Rows Removed by Filter near zero, buffers in the low tens rather than 1.8 million, and sub-millisecond execution.

Verify it yourself

  • Buffers drop from roughly 1.8 million to double digits — an order-of-magnitude change in buffers, not just in wall-clock time, is what proves the index is doing the work.
  • Rows Removed by Filter is 0 or near it; a large value means a predicate is still being re-checked per row and belongs in the index or the partial clause.
  • The rewritten and original predicates return identical rows for a member with an open-ended coverage and for one terminated exactly on $2.
  • pg_relation_size on the partial index is a small fraction of the same index built without the WHERE clause.

Follow-up

  • After the change the plan is still a sequential scan for one particular member. What single query tells you whether that is stale statistics, a genuinely huge row count for that person, or parameter-dependent plan caching?
  • Your generated column needs backfilling on a live table. Does adding a STORED generated column rewrite it, and what does that mean for your maintenance window?
  • How would you serve the same lookup when the answer must be as of a past system-time instant rather than the current belief, given your index is partial on valid_to = 'infinity'?

Fix a paid-amount report that double counts claim versions

Medium · join fan-out · versioned claims · aggregation correctness

claim_line: claim_line_id, claim_id, claim_version smallint, line_number, frequency_code in ('original','replacement','void'), enterprise_person_id, coverage_id, billing_provider_npi, service_from_date, procedure_code, units, billed_amount_cents bigint, allowed_amount_cents, paid_amount_cents, adjudication_status, UNIQUE (claim_id, claim_version, line_number). A monthly report sums paid_amount_cents and joins coverage_span on enterprise_person_id with date overlap to attribute a plan. It overstates payment by roughly 9%, and reconciliation against remittance fails. Name both causes, then write the corrected query returning paid cents and plan per billing_provider_npi for March 2026 service dates.

Approach

  1. Cause one: the SUM spans every claim version. A replacement supersedes a prior claim and a void cancels one, so a replaced claim contributes twice and a voided claim contributes once when it should contribute nothing. Resolve the surviving version per claim_id before aggregating, never by deleting superseded rows.
  2. Cause two: the coverage join is one-to-many. coverage_span carries a system-time version per coverage period, so a person with three beliefs about one period multiplies every claim line by three. The join is for a label, not for filtering, so it must not be allowed to change cardinality.
  3. Collapse the version chain with DISTINCT ON (claim_id) ... ORDER BY claim_id, claim_version DESC, then join the lines back on (claim_id, claim_version), and exclude the surviving version when its frequency_code is 'void'.
  4. Make the coverage lookup cardinality-safe with a LEFT JOIN LATERAL ... LIMIT 1 pinned to valid_to = 'infinity' and ordered by valid_from DESC. LATERAL with LIMIT 1 cannot fan out by construction; a plain join can, and no amount of later DISTINCT repairs a SUM that already doubled.
  5. Keep the money in integer minor units the whole way. SUM(bigint) returns numeric in PostgreSQL, so there is no overflow and no binary floating-point error; cast to a display type only at the edge.

Follow-up

  • A replacement arrives before its original. Your surviving-version logic picks max(claim_version) — what does the report show in the window before the original lands, and does the number self-correct?
  • Add a claim_line_adjustment table with several reason codes per line. What does that do to this query, and where do you aggregate to keep it safe?
  • Reconciliation is still off by 400 cents. Which of the two causes could still produce that, and what is your next query?

How do you approach REST-API design and webhook integration using Python?

Medium

Approach

  1. Say what you would check first and why it is the highest-information step.
  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?

How do you approach troubleshooting issues that span across software, electrical, and mechanical boundaries?

Medium

Approach

  1. Say what you would check first and why it is the highest-information step.
  2. State your assumptions explicitly before working the problem.
  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?

Design an error taxonomy for a real-time eligibility check

Easy · error taxonomy · timeouts · graceful degradation

The registration desk calls GET /eligibility?person_id=&plan_id=&service_date= before a visit. The service answers from locally held coverage_span rows or by an external inquiry that takes seconds and sometimes fails outright. Staff must then proceed, proceed with an unverified flag, or stop, and the screen cannot make that call on its own. Define the response and error taxonomy: which outcomes are 2xx with a payload, which are 4xx, which are 5xx, what each carries, which are safe to retry, and what the desk is instructed to do for each. State the timeout budget.

Approach

  1. Separate the three things a boolean conflates: covered, not covered, and could not determine. Only the first two are answers. Flattening the third into 'not covered' produces a wrong financial answer wearing the clothes of a determinate one, and the desk cannot tell the difference.
  2. Put every determinate and indeterminate answer on 200 with a discriminated payload: {determination: covered | not_covered | indeterminate, reason_code, as_of, computed_at, source: local | payer | cache, coverage_order, cost_share, next_action}. The transport status describes the call; the payload describes the coverage. A not_covered answer is a successful call.
  3. Reserve 4xx for caller faults a retry cannot fix and make them distinguishable, because the remedy differs: 404 unknown person, 422 malformed or out-of-range service_date, 403 when the access decision refuses the purpose of use. Nothing retryable belongs in 4xx.
  4. Treat an external timeout as an outcome, not a transport failure. Return 200 with determination=indeterminate, reason_code=payer_timeout, and the locally derived coverage_span answer as of service_date alongside it. A 504 here throws away a usable degraded answer the desk could act on. Keep 5xx for the case where even the degraded answer could not be produced.
  5. Fix the budget end to end and state who enforces it: a roughly 2s desk interaction budget, a hard 1500ms deadline on the external inquiry, the remainder for the local path, and a client timeout set strictly above the server's so the server decides the fallback rather than the client abandoning a call that was about to degrade correctly. Note that every outcome here is retry-safe because the call is a read whose only side effect is the access audit row.

Worked solution (20 min)

  1. Write the full payload for a covered answer, including as_of, computed_at and the coverage_order that drives coordination of benefits.
  2. Enumerate every failure the call can hit - unknown person, bad date, consent refusal, no local data, payer timeout, payer error, our own fault - and assign each a status code and a reason code.
  3. For each row write the one sentence the desk sees and the action offered, then check that no two causes collapse into the same action.
  4. Do the deadline arithmetic: total budget, external deadline, remaining local budget, and the client timeout that must exceed the server's.
  5. State the retry rule per class: retryable, not retryable, or retryable with backoff only.

Expected result: 200 for covered, not_covered and indeterminate, each carrying {determination, reason_code, as_of, computed_at, source, next_action}; 404 unknown person; 422 malformed or out-of-range service_date; 403 consent or purpose-of-use refusal; 502 or 504 only when not even the degraded local answer could be produced; 500 for our own faults. A payer timeout is 200 + indeterminate + payer_timeout, carrying the local coverage_span answer as of service_date. External deadline 1500ms inside a 2s desk budget, with the client timeout set above it.

Verify it yourself

  • No single field doubles as both the transport result and the coverage answer.
  • Every 4xx names an input the caller can change, and nothing retryable is a 4xx.
  • An indeterminate answer still carries an as_of date and whatever local coverage was readable.
  • The client timeout is strictly greater than the server's external deadline, so degradation is the server's decision.

Follow-up

  • Two payers answer and disagree about which is primary. What does the endpoint return?
  • How long may an eligibility answer be cached, and what exactly is the cache key?
  • The desk proceeded on an indeterminate answer and the claim denies six weeks later. What in the response makes that decision reconstructable?

Gateway instances OOM every five days with flat traffic

Medium · memory leak · heap profiling · idempotency

Interoperability gateway instances are OOM-killed with exit 137 every five to six days. Resident memory grows about 400MB a day per instance and never falls; restarting resets it. Message throughput, connection count and partner mix are unchanged over that period. The gateway keeps an in-process set of recently seen idempotency keys — (sending facility, sending application, message control ID, event timestamp) — in front of the durable ledger. Give the ordered investigation and say what evidence would rule the idempotency cache out.

Approach

  1. Establish that this is a retained-object leak before profiling. Compare resident memory with live heap measured immediately after a forced collection, sampled daily. Resident memory rising while post-collection live heap stays flat points at allocator fragmentation, off-heap or native buffers, or thread stacks, and a heap dump will implicate the wrong object. Rising post-collection live heap is a genuine leak.
  2. Find what the growth is linear in. Plot daily growth against messages processed, connections accepted and wall-clock hours across instances carrying different traffic. Proportional to messages means something is retained per message; proportional to connections means per-connection state is not released on abnormal close, such as a half-open socket whose FIN never arrives.
  3. Diff two heap dumps taken six hours apart on the same instance, sorted by retained size, and identify the dominator. An unbounded set or map shows up as one root holding millions of small entries. This is a five-minute answer once you have the dumps, which is precisely why the first two steps exist: they tell you whether the dump can answer the question at all.
  4. Check the idempotency cache directly against arithmetic rather than suspicion. At single-digit millions of messages a day and a key of roughly 80 bytes plus per-entry overhead, an unevicted set grows by hundreds of megabytes a day, which matches the observed 400MB. A live gauge of its entry count that stays flat is the evidence that rules it out; a gauge tracking messages processed convicts it.
  5. Fix and restore the invariant: bound the cache by size or by a time window, and keep the durable unique constraint as the authority. An in-process set is a latency optimisation and cannot be the correctness mechanism, because it does not survive a restart and is not shared across instances — so a replay right after a deploy would bypass it entirely.
  6. Verify over 48 hours that post-collection live heap is flat, and alert on the cache's entry-count gauge rather than on resident memory, which only tells you after the growth has already happened.

Follow-up

  • How long must the time window be? Tie the number to the sender's replay behaviour after an interface restart and to how far back the broker redelivers.
  • If growth had tracked connections instead of messages, what would you look at first, and how would a half-open MLLP connection show up in the socket table?

Suggested practice checklist

For someone who has spent the last few years shipping features and reading other people's code, and who has not solved a timed problem from a blank file in a long time. Five days rebuild the primitives and the patterns that sit on them, working from invariants rather than remembered solutions, and the last two attach that back to the rest of the loop.

Day 1 — Rebuild the primitives by implementing them

  • Implement a dynamic array with doubling growth and an operation counter, then change the growth rule to add a fixed sixteen slots instead, and time both for n of ten thousand, a hundred thousand and a million. The fixed-increment version resizes n/16 times at O(n) each, so its total work is quadratic; doubling is what makes append amortised constant.
  • Implement a hash map with separate chaining and a load-factor resize, then insert ten thousand keys engineered to land in one bucket and record what happens to lookup time, so that average-case O(1) becomes a claim with a stated precondition rather than a reflex.
  • For dynamic-array append and hash-map insert, write down which cost is amortised rather than worst-case, which single operation pays the whole bill, and what a system with a hard per-operation deadline would have to do instead.

Deliverable: Two working implementations plus a timing table showing the input at which each structure's advertised complexity stops holding.

Day 2 — Arrays under an invariant: two pointers, sliding window, binary search

  • Solve longest-subarray-with-sum-at-most-K using a sliding window, then run it on an input containing negative numbers and watch it return the wrong answer: extending the window only moves the sum monotonically when every element is non-negative, and that precondition is the whole reason the technique works.
  • Write the binary search that finds the first index satisfying a predicate rather than an exact value, put the loop invariant above the loop in a comment, and verify termination on the two inputs that break careless versions: the empty range, and a range where every element satisfies the predicate.
  • Compute the midpoint as lo + (hi - lo) / 2 and write one line on why the obvious (lo + hi) / 2 is a genuine defect in a fixed-width integer type and a non-issue in a language with arbitrary-precision integers.

Deliverable: Three solved problems, each with its invariant written above the loop, plus one recorded input on which the sliding window is provably wrong.

Day 3 — Sorting, heaps, and the greedy argument that has to be proved

  • Solve one top-k problem three ways, by full sort, by a size-k heap, and by quickselect, then write the values of n and k at which each becomes the right choice, along with quickselect's quadratic worst case and why a randomised pivot makes that unlikely rather than impossible.
  • Implement bottom-up heapify and count sift-down steps to confirm it does linear work rather than n log n, because most nodes sit near the bottom of the tree and therefore move only a short distance.
  • Take interval scheduling by earliest finishing time and write the exchange argument out in full: given any optimal schedule, swapping in the earliest-finishing interval keeps it feasible and no smaller. Then construct the weighted variant where that same greedy fails and name what has to replace it.

Deliverable: A three-way top-k comparison with measured crossover points, one written exchange argument, and one counterexample to a greedy rule that looks almost identical.

Day 4 — Recursion, memoisation, and the step to a table

  • Take one problem with overlapping subproblems, such as edit distance or coin change, instrument the plain recursion with a call counter to show the blow-up, then add memoisation and re-count.
  • Convert the memoised version to a bottom-up table and state the two properties you relied on: each subproblem's result depends only on its arguments, and the dependencies form a DAG you can enumerate in order.
  • Rewrite one deep recursion with an explicit stack, then find the input length at which the original hits the interpreter's frame limit, which defaults to about a thousand frames in CPython, so you know when the rewrite is required rather than decorative.

Deliverable: One problem in three forms, naive, memoised and tabulated, with call counts for each and the input length at which recursion depth becomes the binding constraint.

Day 5 — Graphs, where most of the work is choosing the traversal

  • Implement BFS and DFS over one adjacency list, then answer for each which finds a shortest path in an unweighted graph and which you would use to detect a cycle in a directed graph, including why the in-progress versus finished distinction matters for the second.
  • Implement topological sort by in-degree, feed it a graph containing a cycle, and confirm the failure signature is that fewer than V nodes come out rather than an exception, then note that the order it produces is one of several valid ones.
  • Run a shortest-path search on a graph with a single negative edge weight and show the wrong answer, then write the precondition Dijkstra actually needs, non-negative weights, because it finalises a node's distance the first time that node is popped, and name the algorithm you would switch to and its own limit.

Deliverable: A small graph library with BFS, DFS and topological sort, plus two inputs that produce documented wrong answers under the wrong algorithm choice.

Day 6 — One day for everything that is not an algorithm

  • Sketch one system only to the depth a coding-heavy loop tends to reach: the endpoints, what the service stores, and the single query pattern that decides the schema. Stop at twenty-five minutes.
  • Prepare the project answer for an interviewer who codes, which means rehearsing the two levels they push to: the specific thing you built, and why you chose that approach over the alternative they will name. Open with a number and be ready to say what it excludes.
  • Prepare the answer to what you would do differently, choosing a real technical mistake with a specific fix rather than a complaint about process or staffing.

Deliverable: One design sketch at endpoint-and-schema depth, plus a project answer rehearsed to two levels of follow-up.

Day 7 — Solve out loud, under time

  • Do three timed problems at twenty-five minutes each in a plain editor with no autocomplete and no execution until the end, then tally separately the failures that were syntax and the ones that were approach, because those two numbers call for different fixes.
  • Narrate one solution from the first sentence, stating the approach and its complexity before writing any code, and rehearse the sentence you will use when you realise mid-solution that the approach is wrong.
  • Re-solve from blank the two problems you were slowest on this week and compare the times against the day they first appeared.

Deliverable: A recording of one fully narrated solution and a tally that separates syntax failures from approach failures.

Know where your numbers came from

Bring the two or three numbers the story rests on and know how they were collected. A p99 whose timer starts inside your handler excludes the time a request spent queued, so it can sit flat while users wait longer. Give the window, the percentile and what the measurement left out, or drop the number.

What is your experience with streaming packages such as GStreamer in real-time environments?

Medium · behavioural and engineering judgement

Approach

  1. Pick a story where you made the decision, not one where you watched it.
  2. Close with what you would do differently, concretely.
  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?

Can you discuss your experience working with Ubuntu or other Linux distributions in production environments?

Medium · behavioural and engineering judgement

Approach

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

Follow-up

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

How do you handle situations where project direction or requirements shift unexpectedly?

Medium · behavioural and engineering judgement

Approach

  1. Name the disagreement and how you resolved it with evidence.
  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

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

What motivates you to work specifically in the medical device and robotics space?

Medium · behavioural and engineering judgement

Approach

  1. Give the blast radius: what could have broken, and what you measured.
  2. Close with what you would do differently, concretely.
  3. Pick a story where you made the decision, not one where you watched it.

Follow-up

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