cswork / libraryOpen practice workspace

Interview guides · Alaska Native Tribal Health Consortium

Alaska Native Tribal Health Consortium Software Engineer Interview Questions & Guide 2026

Concurrency drills for Alaska Native Tribal Health Consortium software engineer prep: races, lock ordering, and what a check-then-act sequence assumes.

The 60-second brief

As a Software Engineer at the Alaska Native Tribal Health Consortium (ANTHC), you play a vital role in supporting the health and well-being of Alaska Native and American Indian people. You are not just writing code; you are building and maintaining the digital infrastructure that enables healthcare delivery across one of the most geographically challenging regions in the world. Your work directly impacts the systems that provide medical services, manage patient data, and facilitate communication between providers and the communities they serve.

Getting the code to run is the floor. What usually separates answers is the case checked without prompting: empty input, a single element, duplicate keys, or a value that overflows the integer type you chose.

Focus areas

  • Authorise every identified read against current consent
  • Hold accumulator limits under concurrent claim adjudication
  • Model bitemporal coverage and retroactive eligibility changes

Overview

This role requires a blend of technical proficiency and a deep understanding of the unique cultural and environmental context of Alaska. You will be expected to navigate complex systems while remaining adaptable to the mission-driven nature of the organization. The work is both challenging and rewarding, offering the opportunity to contribute to projects that have a tangible, positive effect on public health outcomes. Candidates who thrive here are those who possess both strong engineering discipline and a genuine commitment to the mission.

Because the work environment is mission-centric, your ability to articulate how your technical skills serve the broader health goals of the organization is as important as your coding ability.

The Alaska Native Tribal Health Consortium interview process

Alaska Native Tribal Health Consortium candidates report 2 rounds · ≈ 2-4 weeks. The stages below are what candidates describe, not a published process.

Panel Interview

Coding rounds mostly set a floor. They decide whether you clear the bar, not where you land on the ladder. Level tends to come out of the design discussion and the ownership stories, so the question worth auditing beforehand is whether the scope you describe matches the scope of the job. Work that stops at your own service, or a story whose hard part was writing the code rather than getting several people to agree on an interface, reads a level below where you think you are interviewing, and that gap is usually resolved downwards.

What to demonstrate

  • Whether the largest thing you describe owning ran end to end — the decision, the migration path, the rollout, and what you did when it went wrong — or stopped at the change you merged
  • Whether design answers include what you would not build, what you would defer, and what you would measure before committing, rather than only what the boxes are
  • Whether a disagreement in a story was settled with something checkable — a benchmark, a prototype, a written proposal — instead of by seniority or by waiting it out
  • Whether you can say which calls you made alone and which you escalated, and why the line sat where it did

How to prepare

  • Write your largest piece of owned work as a timeline of decisions — who decided what, when, and what you did when the plan broke — then delete every sentence whose subject is "we" and see how much survives
  • Take one system you know well and drill the migration answer: how old and new paths run side by side under live traffic, how you compare their outputs, what the rollback is once writes are going to both, and which step you would not automate
  • Map each line of the ladder in the job posting to a specific thing you have done, find the line you cannot support, and prepare the closest evidence you have plus an honest account of the gap

Follow-up Communication

You cannot drill a format you do not know, so put the preparation into material that travels. Three pieces of your own work, each rehearsed until you can take a follow-up you did not anticipate, will carry a conversation or a code walkthrough equally well. Specificity is what separates that from filler. A number needs its definition before it means anything: a p99 is over some window and measured at some hop, and a server-side figure excludes the queueing and network time a client would see. The number you cannot qualify is the one to leave out.

What to demonstrate

  • Whether your examples carry detail only someone who did the work would hold, such as what the binding constraint actually was, which alternative you rejected and why it was worse, and what you measured on each side of the change
  • Whether a number survives one follow-up, meaning you can say what it was measured over and whether it moved because of your change or merely alongside it
  • Whether a failure is described with the specific change that followed it, rather than a lesson stated in general terms
  • Whether your part in a team effort is stated accurately, including what other people did

How to prepare

  • Write a page on each of three projects covering the constraint, the option you rejected, the measurement before and after, and what went wrong. Cut any line you cannot take a follow-up on, since you are writing the parts you will be pressed on rather than a summary.
  • Recover the real figures while you still have access: request volume, data size, latency with its percentile and window, team size, timeline. Note where each came from, whether a dashboard, a design document or memory, and mark the estimates so you can say which they are out loud.
  • Take your weakest project story to someone who works in a different area and have them ask why four times in succession. The point where you run out of answer is the part to go and re-read before the round.

Where candidates lose points

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.

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

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

Abandoning working code to chase the optimal solution

Get the straightforward version correct, state its complexity, and only then optimise, keeping the working version until the faster one passes the same cases. A correct quadratic solution with a stated path to linear beats a half-written optimal one that never ran.

Never running a concrete value through the code

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

Questions & practice

Group transfer-chained encounters into episodes with out-of-order arrival

Medium · graph traversal · cycle detection · memoisation

You have up to 5 million encounter rows for a reporting month, in arbitrary order: encounter_id, enterprise_person_id, facility_id, encounter_class, admit_ts, discharge_ts (nullable), status, prior_encounter_id (nullable, set on transfer), source_event_ts. prior_encounter_id may reference a row that arrives later or never arrives. Group rows into episodes, one per maximal transfer chain, returning episode root, person, earliest admit_ts, latest discharge_ts (null if any member is still open) and member count. Report cycles instead of following them. Target O(N) expected time.

Approach

  1. Recognise the shape: every row has at most one prior_encounter_id, so this is a forest of parent pointers, not a general graph. A hash map from encounter_id to row is the whole index you need, and the work is O(N) expected rather than anything sort-shaped.
  2. Resolve roots by iterative pointer-following with write-back memoisation: from each node walk prior_encounter_id until you reach a node with no parent, a parent absent from the input, or a node whose root is already known, then stamp the discovered root onto every node on that path. Each node is finalised once, so total work stays O(N) even when one chain is very long.
  3. Do not recurse. A malformed feed can produce a chain tens of thousands deep, and a recursive resolver dies on stack depth taking the whole batch with it. An explicit loop with write-back is both faster and bounded.
  4. Detect cycles with three-state marking: unvisited, on the current path, finalised. On reaching a node that is on the current path, emit a data-quality record naming every encounter_id in the cycle and exclude that component from the episode output. Choosing an arbitrary root instead would produce an episode that looks plausible and will be believed.
  5. Keep a dangling prior_encounter_id distinct from a genuine root. It means the chain starts outside the window or has not arrived, so flag the episode left-truncated and carry that flag into the output: an episode with an unknown start cannot be used for length of stay or readmission counting without biasing both downward.
  6. Compute aggregates during the same pass that assigns roots: minimum admit_ts, maximum discharge_ts with null propagation so any open member leaves the episode open, and exclude cancelled and entered_in_error members from the aggregates while keeping them retrievable.

Worked solution (25 min)

  1. Load all rows into a hash map keyed by encounter_id, with a colour array alongside.
  2. For each unfinalised node, walk parents onto an explicit path stack until you hit a root, a dangling reference, a finalised node or an on-path node.
  3. Stamp the resolved root, or the cycle marker, back onto every node on the path.
  4. Fold each node into its root's aggregate: min admit_ts, max discharge_ts with null propagation, member count, left-truncated flag.
  5. Emit episodes and the cycle records as two separate outputs.

Expected result: Rows arriving as E3 (prior E2), E1 (prior null), E2 (prior E1) form one episode rooted at E1 with three members, admit from E1 and discharge from E3. A row E9 whose prior E8 never arrives forms a one-member episode flagged left-truncated, and its admit_ts is not the episode's true admission. A pair E5 and E6 pointing at each other yields no episode and one cycle record naming both.

Verify it yourself

  • Episode member counts plus cycle members sum to N exactly.
  • Every episode has exactly one member whose prior_encounter_id is null or dangling.
  • Shuffling the input produces identical episodes: same roots, same member counts, same aggregates.
  • No episode has its earliest admit_ts after its latest discharge_ts. When one does, comparing facility_id on those two rows separates a wrongly-built chain from genuine clock skew between facilities.

Follow-up

  • Two encounters name each other as prior_encounter_id. What does your detector emit, and who needs to see it?
  • One episode spans two facilities whose clocks differ by four seconds. Do you order on source_event_ts or admit_ts, and what does the choice cost?
  • A chain is cut in half by the month boundary. What does this month's report say, and how do you make next month's agree with it?

Sum surviving claim versions in one pass over unordered lines

Easy · hash map · versioning · streaming aggregation

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

Approach

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

Follow-up

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

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?

Bitemporal coverage table with retroactive termination queries

Easy · bitemporal · schema design · retroactive correction

You maintain coverage_span: coverage_id, enterprise_person_id, payer_id, plan_id, effective_date, termination_date (nullable, meaning open-ended, not unknown), status, valid_from timestamptz, valid_to timestamptz DEFAULT 'infinity'. An enrolment file received on 2026-03-05 terminates a member effective 2026-03-01, after a claim for service date 2026-03-03 was already adjudicated against the answer then in force. State your termination_date convention, write the two SELECTs — (a) is the person covered on 2026-03-03 as known now, (b) what the system believed on 2026-03-04 — and say precisely what the loader writes when the file lands.

Approach

  1. Separate the two time axes explicitly before writing anything: effective_date/termination_date are business time (when benefit applied), valid_from/valid_to are system time (when this row was the system's belief). Every query names both, or it is ambiguous.
  2. Pin the inclusivity convention, because the predicate depends on it. Enrolment files conventionally carry termination_date as the last covered day, so the business-time predicate is effective_date <= :svc AND (termination_date IS NULL OR termination_date >= :svc). If your feed uses an exclusive end, it becomes > :svc, and a half-open daterange column removes the ambiguity permanently.
  3. Write the as-known-now query by pinning system time to the open version (valid_to = 'infinity') and letting business time float to the service date.
  4. Write the as-of-belief query by pinning system time to an instant: valid_from <= :asof AND valid_to > :asof. Half-open on both ends is what makes 'exactly one row per key per instant' provable.
  5. Describe the write as one guarded statement rather than two independent ones: a data-modifying CTE whose UPDATE stamps the open row's valid_to to now() (the only UPDATE the design permits — it stamps system time, it never edits business facts) and returns that row's keys, with the INSERT of the new version selecting FROM that CTE. Feeding the INSERT from the UPDATE's RETURNING is what makes a replayed file a no-op; an unconditional INSERT leaves the partial unique index to raise 23505 instead, which is a backstop rather than idempotency. The pre-correction row stays readable either way, which is what makes the already-adjudicated claim defensible.

Worked solution (25 min)

  1. Write the DDL with both ranges half-open in system time: valid_from timestamptz NOT NULL, valid_to timestamptz NOT NULL DEFAULT 'infinity', and a partial unique index on (enterprise_person_id, plan_id, effective_date) WHERE valid_to = 'infinity' so only one belief is open per business period.
  2. Write query (a) against the open system-time version, with the business-time predicate on the service date.
  3. Write query (b) by replacing valid_to = 'infinity' with the half-open instant predicate, keeping the identical business-time predicate so the only thing that changed is which belief you read.
  4. Write the loader as one statement: WITH closed AS (UPDATE ... RETURNING ...) feeding INSERT ... SELECT FROM closed. Guard the UPDATE with valid_to = 'infinity' so a concurrent loader cannot close the same row twice, and with a termination_date IS DISTINCT FROM :new_term test so a replay of the same file matches no row and therefore inserts none. Under READ COMMITTED a second loader that blocks on the row lock re-evaluates the guard against the newly committed version, fails it, and writes nothing.
  5. Trace the 2026-03-05 file by hand: before it, one row (effective 2026-01-01, termination NULL, valid_to infinity); after it, two rows, and confirm (a) returns not-covered while (b) returns covered.

Expected result: (a) SELECT coverage_id, plan_id, coverage_order FROM coverage_span WHERE enterprise_person_id = :p AND valid_to = 'infinity' AND status = 'active' AND effective_date <= DATE '2026-03-03' AND (termination_date IS NULL OR termination_date >= DATE '2026-03-03'); returns no row. (b) the same query with valid_to = 'infinity' replaced by valid_from <= TIMESTAMPTZ '2026-03-04 00:00+00' AND valid_to > TIMESTAMPTZ '2026-03-04 00:00+00'; returns the pre-correction row with termination_date NULL. The loader runs as one statement: WITH closed AS ( UPDATE coverage_span SET valid_to = now() WHERE enterprise_person_id = :p AND plan_id = :pl AND effective_date = DATE '2026-01-01' AND valid_to = 'infinity' AND (termination_date IS DISTINCT FROM DATE '2026-03-01' OR status IS DISTINCT FROM 'terminated') RETURNING enterprise_person_id, payer_id, plan_id, effective_date ) INSERT INTO coverage_span (enterprise_person_id, payer_id, plan_id, effective_date, termination_date, status, valid_from, valid_to, source_file_id) SELECT enterprise_person_id, payer_id, plan_id, effective_date, DATE '2026-03-01', 'terminated', now(), 'infinity', :f FROM closed;

Verify it yourself

  • For any fixed instant, the system-time predicate selects exactly one row per (person, plan, business period): SELECT enterprise_person_id, plan_id, effective_date, count() FROM coverage_span WHERE valid_from <= :t AND valid_to > :t GROUP BY 1,2,3 HAVING count() > 1 returns zero rows.
  • Query (b) with :asof = now() returns exactly what query (a) returns — if it does not, the close-and-insert is not atomic or the half-open boundaries overlap.
  • No row's valid_from equals its valid_to, and no two versions of one coverage_id have overlapping [valid_from, valid_to).
  • Replaying the same enrolment file inserts nothing and raises nothing: the guarded UPDATE matches no row because the open version already carries termination_date 2026-03-01 and status 'terminated', and the INSERT selects FROM that UPDATE's RETURNING, so it has zero input rows. Assert the person's row count is unchanged. A loader that instead runs the INSERT unconditionally fails the replay with SQLSTATE 23505 from the partial unique index — that stops the duplicate, but it is an error the caller must handle, not an idempotent replay.

Follow-up

  • The claim adjudicated on 2026-03-03 is now retroactively uncovered. What does reprocessing have to read — the belief at adjudication time or the current truth — and what does the remittance reversal reference?
  • Add the constraint that stops two active coverage versions from overlapping in business time for the same person and plan, without blocking the superseded history rows.
  • A later file reinstates coverage with the same effective_date. How many rows exist for that period afterwards, and which one does query (a) return?

Reproduce and fix a lost update on a deductible accumulator

Medium · isolation levels · lost update · lock contention

An accumulator row holds deductible_applied_cents bigint and plan_deductible_cents bigint, keyed by (enterprise_person_id, plan_id, benefit_year). The adjudicator opens a transaction, SELECTs the row, computes the member's share in application code, then UPDATEs the row to the absolute new total it computed, all under PostgreSQL's default READ COMMITTED. Two claim lines for one member adjudicate concurrently: both charge the member deductible, but the accumulator advances by only one of the two amounts, so the member is billed deductible again on a later line after the plan deductible has already been met. Write the exact two-session interleaving that produces it. Then give three fixes, each naming the lock or isolation level, the SQLSTATE you must handle, and the throughput cost.

Approach

  1. State the anomaly precisely. READ COMMITTED takes a fresh snapshot per statement, so it prevents dirty reads but permits a lost update when a transaction reads a value, computes outside the database, and writes back an absolute result. The vulnerable window is the round trip through application code between two statements, not the transaction boundary — the same logic expressed as one relative UPDATE is safe at this very isolation level.
  2. Write the interleaving as an ordered script both sessions can be replayed from, with the commit points marked, and make both writes absolute (SET deductible_applied_cents = :computed_total). The point is not that two writes happen — it is that the second write stores a total computed from a value that had already been superseded by the time it landed.
  3. Fix one, SELECT ... FOR UPDATE on the read: the second session blocks on the row lock and, under READ COMMITTED, re-reads the newest committed version when it unblocks, so its computation starts from the winner's total. No serialization error to handle. Cost is serialised throughput per member and a lock held for the transaction's whole duration, so nothing inside may call an external payer.
  4. Fix two, REPEATABLE READ or SERIALIZABLE with a retry loop: in PostgreSQL, REPEATABLE READ aborts the second writer of a row with SQLSTATE 40001, 'could not serialize access due to concurrent update'; SERIALIZABLE additionally aborts on read/write dependencies detected by SSI, also under 40001. The retry re-runs the whole transaction, so the claim application must be idempotent or keyed by claim_line_id, or a retry double-applies.
  5. Fix three, single-writer partitioning: route by hash(enterprise_person_id) to one consumer per partition, so no two transactions ever touch one accumulator. No locks, no retries; the cost is head-of-line blocking behind a slow claim, rebalancing during deploys, and losing the ability to scale a hot member.
  6. Name the cheapest option and its real catch. A single UPDATE ... SET deductible_applied_cents = LEAST(plan_deductible_cents, deductible_applied_cents + :amt) ... RETURNING deductible_applied_cents does not lose the update: under READ COMMITTED an UPDATE that hits a concurrently updated row blocks, then re-evaluates its expressions against the newly committed version, so both increments land. The catch is that the member's share is the delta this statement actually applied, which is the returned value minus the row's pre-image — and RETURNING does not expose the pre-image before PostgreSQL 18's OLD/NEW aliases. On earlier versions you must read that pre-image under the same row lock, which puts you back in fix one with a shorter critical section. The one-statement form is a clean fix only when the caller does not need the delta.

Follow-up

  • A claim is reversed six months later under a plan design that has since changed. What amount does the reversal subtract, and where is that number stored?
  • Your retry loop hits 40001 repeatedly for one member during a batch window. What is the backoff, and at what point do you stop retrying and pend the claim?
  • Which of the three fixes survives a retroactive eligibility change that invalidates everything applied in the last month, and what does the rebuild look like?

Are you comfortable working in an Alaskan environment?

Medium

Approach

  1. State your assumptions explicitly before working the problem.
  2. Clarify what is being asked and what a complete answer contains.
  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?

Can you walk us through your technical background and how it applies to our current infrastructure?

Medium

Approach

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

Follow-up

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

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?

Duplicate patients created quietly by a matcher timeout fallback

Hard · partial outage · fallback policy · identity resolution

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

Approach

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

Follow-up

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

Suggested practice checklist

For someone fluent in a dynamic language who has shipped real work but has never had to say what the runtime is doing underneath. The week is built on measuring and deliberately breaking things, because the questions that expose this background are the ones where the interviewer asks why a second time.

Day 1 — Measure before reasoning

  • Take a slow piece of your own code, write down in advance where you believe the time goes, then profile it and record how wrong the guess was. The cost is usually an allocation you did not notice or an accidental quadratic membership test.
  • Replace one list membership test inside a loop with a set and measure at a thousand, ten thousand and a hundred thousand elements, confirming the shape of the curve rather than only that it got faster.
  • Write down the three quantities you can now measure instead of assert: wall time, peak memory, and call count for the function you suspected.

Deliverable: A before-and-after profile of real code plus a written note on the size of the gap between the guess and the measurement.

Day 2 — References, copies, and the bugs they produce

  • Write the function with a mutable default argument, call it three times, and explain the accumulating result: the default is evaluated once when the function is defined, so every call shares one object.
  • Build a nested structure, take a shallow copy, mutate an inner element, and show that both views changed, because a shallow copy duplicates the container and not the elements. Then fix it with a deep copy and state the cost you just accepted.
  • Write two functions, one mutating its argument in place and one rebinding the local name, and predict the caller's view of each before running it. That single distinction produces most of the bugs that pass their tests.

Deliverable: Three small programs whose output you predicted correctly before running, each with a one-line statement of the rule underneath.

Day 3 — Types, once, in a language that checks them

  • Port one module you have already written, roughly a hundred lines, into a statically typed language, and record every place the compiler demanded an answer your original had left implicit: a value that can be absent, a numeric width, a case never handled.
  • Write the same signature in both languages and state what the static one guarantees before the program runs and what it does not, since it will not save you from a wrong algorithm or an index out of range.
  • Write the difference between an interface satisfied by declaration and one satisfied structurally, with one case each where the other approach would miss the mistake.

Deliverable: One module in two languages plus a list of the questions the type checker forced you to answer.

Day 4 — Concurrency, starting with what actually runs at the same time

  • Run the same CPU-bound function across four threads and four processes and measure both. Under the default CPython build the threaded version will not speed up, because only one thread executes bytecode at a time; the process version will. Check which build you are on first, since free-threaded builds remove that lock and change the result.
  • Then run a blocking I/O workload across four threads and measure it speeding up, because the interpreter releases that lock around blocking calls, which is why treating threads as useless is wrong as a general claim.
  • Build the lost update: two threads each incrementing a shared counter a hundred thousand times, and show a final value below the expected sum, because an increment is a load, an add and a store and the thread can be suspended between them. Fix it with a lock and then measure what the lock costs.

Deliverable: Three measurements, threads against processes on CPU work, threads on I/O work, and a demonstrated lost update, each with the mechanism written underneath.

Day 5 — Debugging as a procedure rather than an instinct

  • Work one real failure as a bisection: find a revision or an input size where it is good and one where it is bad, halve repeatedly, and state the two assumptions bisection needs, that the property changes exactly once across the range and that the test is reliable.
  • Minimise one failing input to the smallest version that still fails, and record how many rounds it took.
  • Keep a hypothesis log for one bug in three columns, what I believe, what would disprove it, what I observed, and stop yourself the first time you are about to change two things at once.

Deliverable: One bug worked to root cause with a written hypothesis log and a minimised reproducing input.

Day 6 — Tests that catch the bug you are about to write

  • Implement an LRU cache with a capacity bound, then write the three test cases that would catch an off-by-one in eviction: insert exactly capacity items and assert nothing was evicted, insert one more and assert the least recently used key is the one gone, and read an old key just before that insert so the eviction victim changes.
  • Add a property test comparing your implementation against a deliberately slow reference, an ordered list scanned linearly, over a few thousand random operation sequences, because a slow reference finds the cases you would not have thought to write.
  • Write one numeric test that fails under exact equality and passes with a tolerance, and state why the tolerance has to be relative rather than absolute once the magnitudes grow.

Deliverable: An LRU implementation with three boundary tests, one property test against a slow reference, and one tolerance-based numeric test.

Day 7 — Debug something broken, out loud

  • Have someone plant three defects in a two-hundred-line program, an off-by-one, a shared mutable state bug, and a wrong error-handling path, then find them while narrating, under a fixed rule: state the hypothesis before touching anything.
  • Time each one and record which tool found it, reading, a printed value, a debugger, or a test, because the question asked in interviews is how you would find it rather than what it was.
  • Write the sentence you will use when you do not yet know the cause, one that names the next measurement instead of offering a guess.

Deliverable: A recorded debugging session with time-to-find per defect and the method that found each.

The failure that was yours

Keep one story where the bad call was yours rather than a dependency's or a manager's. Name the check that would have caught it, whether you added that check afterwards, and whether it has fired since. Answers that route blame outward end the conversation early; answers that end in a guardrail someone still relies on tend to open it up.

What are some past cases where you have worked with Alaska Native populations?

Medium · behavioural and engineering judgement

Approach

  1. Close with what you would do differently, concretely.
  2. State the situation in two sentences and spend the rest on the reasoning.
  3. Give the blast radius: what could have broken, and what you measured.

Follow-up

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

How has your past experience made you an ideal candidate for this position?

Medium · behavioural and engineering judgement

Approach

  1. Name the disagreement and how you resolved it with evidence.
  2. Close with what you would do differently, concretely.
  3. Give the blast radius: what could have broken, and what you measured.

Follow-up

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

Describe a time of professional success.

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. Give the blast radius: what could have broken, and what you measured.

Follow-up

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

Describe a time of professional failure.

Medium · behavioural and engineering judgement

Approach

  1. Name the disagreement and how you resolved it with evidence.
  2. Close with what you would do differently, concretely.
  3. Give the blast radius: what could have broken, and what you measured.

Follow-up

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