Interview guides · Diasorin
Diasorin Software Engineer Interview Questions & Guide 2026
Concurrency drills for Diasorin software engineer prep: races, lock ordering, and what a check-then-act sequence assumes.
The 60-second brief
As a Software Engineer at Diasorin, you play a vital role in building and maintaining the software systems that power critical diagnostic tools, medical devices, and enterprise engineering workflows. Your work directly impacts laboratory efficiency, clinical accuracy, and the reliability of life-saving medical testing equipment. Whether you are developing embedded control software, managing internal networks, or integrating complex diagnostics hardware, your contributions ensure that medical professionals have dependable tools when they need them most.
Treat capacity estimation as a conversion skill rather than a table to memorise: turn a user count and an action rate into requests per second and bytes per day, then name the component that number breaks first. The figure only matters if it changes the design.
Focus areas
- Model bitemporal coverage and retroactive eligibility changes
- Authorise every identified read against current consent
- Design person merges that remain reversible afterwards
Overview
This role sits at the intersection of software development, hardware integration, and rigorous regulatory standards. You will collaborate closely with manufacturing engineers, field service teams, and quality assurance specialists to deliver robust, scalable applications. The problem space requires a high degree of precision, as software failures in medical diagnostics can have immediate real-world consequences. You will navigate complex system requirements, troubleshoot production issues, and design solutions that operate reliably under strict operational constraints.
Expect a stimulating environment where technical excellence meets regulatory accountability. While the work can be demanding due to high standards and compliance requirements, it offers immense professional satisfaction and deep domain expertise. You will tackle multifaceted engineering challenges that stretch your problem-solving capabilities while contributing directly to global healthcare advancements.
The Diasorin interview process
Diasorin candidates report 3 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.
Initial Screening
Before anything technical happens, someone has to decide which rung of the ladder your loop is calibrated to, and that decision sets the bar for every round after it. It comes from how you describe scope, not from your title, because titles do not convert cleanly between companies. The weak version of the answer is team size and years. The strong version names the largest change you shipped where nobody reviewed the design, what would have broken if you had been wrong, and what you were paged for. Get the level said out loud on this call, because the range and the loop both follow from it.
What to demonstrate
- Whether the scope in your own account maps onto a level the team actually has an opening at, so a mismatch ends the process cheaply rather than after four interviewers have spent a day
- Whether your title needs re-mapping: the same word describes very different amounts of independent decision-making at a twenty-person company and a ten-thousand-person one
- Whether your compensation expectation can be filled at that level in the structure the role pays in, which is why the number gets asked for before any engineer is scheduled
How to prepare
- Write down two changes from the last two years: the largest one you designed with nobody reviewing the design, and the largest one where someone more senior did. Lead with the first when scope comes up, and be ready to say which parts of the second were yours
- Ask which level the loop is calibrated to and what changes at the level above it, then plan your weeks from that answer rather than from the posting
- Settle a total-compensation range beforehand with the split named, base against bonus against equity and its vesting period, so a question about numbers gets a number instead of the word market
Technical Interviews
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 Assessments
This round is deciding whether a change you make without supervision can be allowed to reach production. It is scored on what you knew at the moment you decided, not on how it turned out, so a story that opens with the result and works backwards reads as luck retold as judgement. Say what the options were, what you did not know, what you did to shrink the unknown before committing, and what you accepted as the worst plausible case. The detail that separates answers is a bound: how many users, how much data, and for how long, if you had been wrong.
What to demonstrate
- Whether the reasoning you give was available at the time you decided rather than after the result came in, since a story whose deciding evidence arrived later describes an outcome and not a judgement
- Whether you can put units on the exposure (users, rows, minutes of degraded service) and whether the containment you chose actually bounded it: a canary bounds the request path it fronts, while a background job writing to a shared table reaches every user regardless of which version served their requests
- Whether the reversal path existed before you shipped or was improvised during the incident, and whether it restores state or only stops further damage
How to prepare
- For your three largest changes, write down the one thing you would have had to be wrong about for it to fail, and what your best estimate of it was on the day you shipped. If you never held an estimate, that is the gap the follow-up questions will find
- Write the undo procedure for one of those changes as it existed at the time, then mark which steps restore data and which only stop new damage. Turning a flag off or reverting a deploy ends the new writes; rows already written come back only from a copy you kept, and a dropped column comes back empty unless something outside the schema holds the values
- Rehearse one story from the decision point forward and stop before the outcome, then have someone ask what you would do next. If the story only works with the ending attached, it is an anecdote rather than a decision you can defend
Where candidates lose points
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.
Assuming admission, discharge and transfer messages arrive in the order the events happened.
Interface engines route by message type across separate queues and retry independently, so a discharge can land before the admission it closes and an update can land before the registration it modifies. Ordering has to come from the sender's event timestamp plus a per-encounter sequence, and the consumer has to apply out-of-order and late-arriving events correctly rather than rejecting them, because rejection turns a recoverable ordering issue into permanent data loss that nobody notices until a report is short.
Retrying a write that is not safe to repeat
A timeout tells you nothing about whether the server applied the write, so a blind retry of a create or a charge can duplicate it. Either make the operation idempotent, with a caller-supplied key the server deduplicates on or a conditional update, or do not retry it; and use exponential backoff with jitter so the retries of many clients do not synchronise into a second outage.
Starting work without saying what you are about to spend time on
State the plan before executing it: the approach, roughly how long it will take, and what you intend to leave hand-waved. That gives the interviewer a chance to redirect you in ten seconds rather than watching you spend fifteen minutes on the wrong sub-problem.
Questions & practice
What steps do you take to ensure high availability and reliability in mission-critical applications?
Medium · data structures and algorithms
Approach
- State the target complexity and say which constraint rules the naive version out.
- Choose the data structure from the access pattern, not from familiarity.
- Restate the input: its shape, its size, and what is guaranteed about it.
Follow-up
- Which test case would catch an off-by-one here?
- What is the worst case, and how likely is it on real data?
How do you approach debugging intermittent software issues in a tightly integrated hardware environment?
Medium · data structures and algorithms
Approach
- Name the brute-force solution and its complexity before improving on it.
- State the target complexity and say which constraint rules the naive version out.
- Walk one small example through your approach before writing the whole thing.
Follow-up
- Which test case would catch an off-by-one here?
- What is the worst case, and how likely is it on real data?
Walk me through your troubleshooting methodology when an unfamiliar system component fails in production.
Medium · data structures and algorithms
Approach
- Choose the data structure from the access pattern, not from familiarity.
- State the target complexity and say which constraint rules the naive version out.
- Name the brute-force solution and its complexity before improving on it.
Follow-up
- How does this change if the input no longer fits in memory?
- What is the worst case, and how likely is it on real data?
These questions evaluate your hands-on engineering instincts, diagnostic abilities, and practical technical knowledge.
Medium · data structures and algorithms
Approach
- State the target complexity and say which constraint rules the naive version out.
- Choose the data structure from the access pattern, not from familiarity.
- Restate the input: its shape, its size, and what is guaranteed about it.
Follow-up
- What is the worst case, and how likely is it on real data?
- How does this change if the input no longer fits in memory?
Flatten overlapping coverage spans into a primary-payer timeline
Medium · sweep line · intervals · bitemporal
For one enterprise_person_id you hold up to 10,000 coverage_span rows: coverage_id, payer_id, plan_id, effective_date, termination_date (null means open-ended), coverage_order (1 is primary), status, valid_from and valid_to (system time). Given a system instant T and a date range D0 to D1, return disjoint business-date intervals covering that range, each labelled with the active coverage_id of lowest coverage_order, and with uncovered stretches returned explicitly. termination_date is the last covered day. Only status active counts. Target O(N log N) time, O(N) space.
Approach
- Apply the system-time slice before any interval logic: keep rows where valid_from is at or before T and valid_to is after T. That single predicate is what makes the answer 'what we believed at T' rather than 'what we believe now', and a retroactive termination loaded after T leaks straight into the output if you skip it.
- Convert each surviving row to a half-open day interval from effective_date up to termination_date plus one day, using positive infinity for a null termination. Half-open removes every off-by-one at the join between a termination and the next plan's effective date, which is where same-day switches get turned into false gaps.
- Sweep: emit 2N boundary events, sort by date in O(N log N), and hold the active set in a min-heap keyed by (coverage_order, coverage_id) with lazy deletion, popping the top while it has already expired at the current boundary. Each coverage is pushed once and popped once, so the sweep is O(N log N) total and O(N) space.
- Emit an output interval whenever the heap top changes between consecutive boundaries, and emit an explicit uncovered interval when the heap empties. A gap reported as a gap is a different answer from a gap omitted, and downstream the difference is a denial versus a silent assumption of coverage.
- Fix the tie-break and state it: lowest coverage_order, then lowest coverage_id. Two rows at order 1 is a data error, and without a deterministic rule the coordination-of-benefits answer changes between two runs over identical data.
- Clip to D0 and D1 last, so a coverage that starts before D0 still contributes its correct order inside the window.
Worked solution (30 min)
- Filter to rows live at T, then map each to a half-open interval and a (coverage_order, coverage_id) priority.
- Build the boundary list, sort it, and sweep with a lazily-deleted min-heap.
- At each boundary, drop expired heap tops and compare the new top to the previous one, emitting an interval on change.
- Emit an uncovered interval whenever the heap is empty between two boundaries.
- Clip the emitted list to D0 through D1 and assert the intervals are disjoint and contiguous.
Expected result: Window 2026-01-01 to 2026-12-31 with three live rows: coverage 1 at order 1 effective 01-01 terminating 06-30, coverage 2 at order 2 effective 03-01 open-ended, coverage 3 at order 1 effective 09-01 open-ended. Output is three intervals: 01-01 to 06-30 labelled coverage 1, 07-01 to 08-31 labelled coverage 2, 09-01 to 12-31 labelled coverage 3, with no gaps. Delete coverage 2 and the middle interval becomes an explicit uncovered stretch of 07-01 to 08-31.
Verify it yourself
- The emitted intervals are sorted, pairwise disjoint, and their union is exactly D0 through D1 once uncovered stretches are included.
- Every boundary date in the output is D0, the day after D1, some effective_date, or some termination_date plus one day. Any other date means a boundary was synthesised.
- Removing the lowest-order coverage promotes exactly the next order and leaves every boundary it did not touch unchanged.
- Choosing T before the load time of a retroactive termination returns the pre-correction timeline, and T after it returns the corrected one, from the same table.
Follow-up
- Run the same query at two system instants and diff the timelines. What did the correction change, and what does each extra instant cost you?
- A termination arrives with an effective date earlier than the service date of a claim that already adjudicated and paid. What does the timeline now say, and what has to happen to that claim?
- Two enrolment files disagree on coverage_order for a dependent. What does your tie-break do, and what should the system do instead of tie-breaking?
Denormalise consent into a decision table with a stated staleness bound
Hard · denormalisation · cache invalidation · access control
Access governance answers 'may this requester read this person's data for this purpose' on every identified read — tens of thousands per second at a 5ms p99 — from consent_directive: consent_id, enterprise_person_id, version, scope, purpose_of_use text[], grantee_org_id (NULL meaning all organisations), permit boolean, effective_ts, expires_ts, revoked_ts, status. Evaluating the normalised version history per read misses the budget. Design the denormalised decision store and its key, state the revocation staleness bound in seconds and the mechanism that actually enforces it, and say which normalised rows remain the system of record and why.
Approach
- Derive the shape from the read, not from the source table. The read is a point lookup on (person, grantee organisation, purpose), so fan the purpose_of_use array out into one row per purpose and make those three columns the primary key. A GIN index on the array would serve containment queries nobody issues.
- Handle the wildcard concretely. grantee_org_id NULL means 'all organisations', and NULL never equals anything, so a nullable column in the key breaks both the primary key and the equality lookup. Store a sentinel org id of 0 for the wildcard and resolve specific-before-wildcard in the lookup, or the wildcard row is silently unreachable.
- Make refusal a row, not an absence. An explicit permit = false must outrank a permit for the same key, so carry a precedence rank (specific grantee beats wildcard, refusal beats permit at equal specificity) and resolve with ORDER BY rank LIMIT 1. Default deny when nothing matches, so a rebuild failure fails closed.
- Separate the two staleness mechanisms and be precise about which one is the bound. The in-process decision cache has a TTL; a revocation also publishes an invalidation. The publish shortens the typical case to milliseconds, but it can be lost, so the guaranteed bound is the TTL alone — quote that number, for example 30 seconds, and justify it against the cache hit rate you need to hold 5ms p99.
- Evaluate expires_ts at read time against the request clock rather than baking the decision. A cached permit whose expires_ts has passed is wrong regardless of invalidation, because no event fires when a timestamp simply goes by.
- Keep consent_directive as the system of record: audit must reconstruct the directive in force at any past instant, the decision table holds only current state, and a corrupted decision table must be rebuildable by replaying the directives. Bound the write amplification by keeping grantees at organisation granularity and purposes a closed code set, so one directive expands to a known small number of rows rather than an unbounded cross product.
Follow-up
- Break-glass must always succeed and must be unmistakably marked. Where does it sit relative to this lookup, and what stops it from becoming the quiet default path?
- The invalidation bus is down for thirty minutes. Walk through what a revoked person's data exposure looks like minute by minute, and what the backlog does when the bus returns.
- Where does the access audit record get written so that an audit-store outage degrades neither the log's completeness nor the read's availability?
Namespace source identifiers so a cross-facility join cannot collide
Easy · identity · composite keys · constraints
person_identity_link holds one row per (assigning_authority, source_person_id) per link version: link_id, enterprise_person_id, assigning_authority, source_person_id, match_score, link_status in ('auto_linked','manual_linked','potential_duplicate','unlinked','rejected'), version, superseded_by_link_id, decided_by, decided_at. Two facilities in one network both issued medical record number 004821, to different people. An existing extract joins on source_person_id alone and has been merging their charts. Give the constraint set that makes the bare join impossible to write by accident, then write the query that resolves one facility's (authority, MRN) to its current enterprise_person_id.
Approach
- Name the root cause precisely: an MRN is unique only inside the authority that issued it, and a member ID only inside payer plus plan. The identifier is half a key; the namespace is the other half.
- Make the composite the only addressable key. UNIQUE (assigning_authority, source_person_id, version) gives version history; a partial unique index on (assigning_authority, source_person_id) WHERE superseded_by_link_id IS NULL enforces one live decision per source record.
- Remove the bare column as a join target from every consuming view: expose a view or function that takes both arguments, so a one-argument lookup is a compile-time error rather than a silent cross-patient merge.
- Write the resolution query against the live version only, and filter link_status separately — 'unlinked' and 'rejected' are live rows that mean 'no enterprise identity', so they must not be confused with 'no row'.
- State the detection query you would run before trusting any existing extract: group source_person_id across authorities and count distinct authorities, which surfaces every collision already in the data.
Worked solution (20 min)
- Write the two constraints and say which invariant each one holds: the three-column UNIQUE holds version history, the partial unique index holds single-live-decision.
- Write the resolution query filtering on superseded_by_link_id IS NULL plus the two linked statuses.
- Write the collision detector over the existing table and run it mentally against the 004821 case to confirm it returns that value with two authorities.
- Decide the contract for zero rows versus a live 'unlinked' row: zero rows means the source record was never seen, 'unlinked' means it was seen and deliberately not attached, and the caller must handle them differently.
- Write the one-line fix for the broken extract: add the authority column to the extract and to the join, then re-run and diff the row count against the old output.
Expected result: Constraints: UNIQUE (assigning_authority, source_person_id, version) and CREATE UNIQUE INDEX ON person_identity_link (assigning_authority, source_person_id) WHERE superseded_by_link_id IS NULL. Resolution: SELECT l.enterprise_person_id FROM person_identity_link l WHERE l.assigning_authority = :aa AND l.source_person_id = :mrn AND l.superseded_by_link_id IS NULL AND l.link_status IN ('auto_linked','manual_linked'); zero or one row by construction. Detector: SELECT source_person_id, count(DISTINCT assigning_authority) AS authorities, array_agg(DISTINCT assigning_authority) FROM person_identity_link GROUP BY 1 HAVING count(DISTINCT assigning_authority) > 1;
Verify it yourself
- The detector returns 004821 with two authorities, confirming the collision is in the data and not a theory.
- Inserting a second live row for one (authority, MRN) raises a unique violation, while inserting a superseded row for the same pair succeeds.
- The resolution query returns at most one row for every (authority, MRN) pair in the table — run it as a LATERAL over the distinct pairs and assert no count exceeds one.
- Re-running the repaired extract yields a strictly different enterprise_person_id for at least one of the two 004821 patients.
Follow-up
- The spine stores enterprise_person_id directly on encounter and claim_line. What does a wrong merge cost you with that design, and what would you store instead to keep unmerge a supported operation?
- A link is downgraded from auto_linked to potential_duplicate. Should the resolution query return the old enterprise identity, nothing, or an error, and who decides?
- Your partial unique index blocks a second live row. How does the merge path insert the new version and supersede the old one without violating it mid-transaction?
Composite patient summary reads over versioned clinical resources
Medium · fan-out · tail-latency · partitioning · versioning
The longitudinal record service serves a patient summary that fans out across eight to twelve resource types — encounters, observations, orders, medications and others — over billions of versioned rows, where a correction inserts a new version and points at the row it supersedes rather than updating in place. Reads outnumber writes about twenty to one. The number that matters is the p99 of the composite read, not of any single resource call. Give the partitioning key, the index and predicate that select current versions, and the mechanism that stops one slow resource type from setting the whole summary's p99.
Approach
- Partition by enterprise_person_id on a hash, because every read in this path is patient-scoped: each resource type's fan-out then lands on one partition, and no summary becomes a scatter-gather across the whole keyspace. Partitioning by time instead optimises the analytical query at the expense of the query that runs twenty times per write.
- Make current-version selection a cheap predicate rather than a computed one. NOT EXISTS against supersedes_observation_id is an anti-join on every read; a superseded_at column written in the same transaction as the correction, with a partial index on (enterprise_person_id, loinc_code, collected_ts DESC) WHERE superseded_at IS NULL, turns it into an index range scan. You pay one extra write per correction and take on the obligation to keep the flag consistent with the supersedes pointer.
- Do the tail arithmetic before choosing a mechanism. If ten subcalls are independent, the probability that at least one exceeds its own p99 is 1 - 0.99^10, about 9.6%, so the composite p99 sits near each subcall's p99.9. Independence is the precondition and shared storage makes it optimistic, so budget backwards from the composite target to a per-call p99.9.
- Attack the tail rather than the median: issue subcalls concurrently with a per-call deadline well inside the composite budget, hedge to a second replica when a call passes its p95, and return the summary with a per-section unavailable marker when a non-critical resource type misses. A summary missing one section is clinically usable; a summary that arrives four seconds late is not.
- State what you will not do: no long-lived cache of the assembled summary. Amendments and corrections are ordinary traffic, and a stale composite is exactly the failure the versioned model exists to prevent. Cache per resource version under an immutable key instead, so a correction produces a new key rather than requiring invalidation.
Follow-up
- A correction commits midway through the fan-out, so one section reflects it and another does not. What consistency do you promise the reader, and how do you express it in the response?
- Hedging doubles load on the slowest replica at precisely the worst moment. How do you bound the hedge rate without losing its benefit?
Evolve a coverage contract to bitemporal without breaking shipped clients
Hard · versioning · backward compatibility · bitemporal
GET /coverage/{person_id} has for years returned a flat list of {payer_id, plan_id, effective_date, termination_date, status}, where a null termination_date means open-ended. Callers now need to ask coverage questions as of a past service date, and to see what the system believed at an earlier instant, because retroactive terminations are routine traffic. Dozens of deployed clients cannot be upgraded on our schedule and some reject unknown fields. Design the evolution: the new shape, the versioning mechanism, the default behaviour for an old client, and the deprecation path with the checkpoints that gate it.
Approach
- Classify the change before choosing a mechanism. Adding a field is safe only under two preconditions: no client validates against a closed schema, and no client reads the object and writes it back, because a round-trip silently drops fields the client does not model. The prompt states some clients reject unknown fields, so 'just add as_of' is not additive here - that is the whole difficulty.
- Separate the two time axes the new shape must expose, since conflating them is the usual bug. Business time answers which coverage period applies to a service date; system time answers what we believed at an instant. The new resource takes both - an as_of date and an asserted_at instant - and the old response is exactly the projection as_of = today, asserted_at = now.
- Choose the version carrier and defend it. A new path is blunt but unmistakable and caches cleanly; a media-type parameter negotiates per request but is ignored by clients that do not set Accept and by any cache not keyed with Vary; a query parameter is trivial to set and pollutes cache keys. A new resource path for the shape change plus strictly additive evolution within each version is the defensible combination, and under any choice the old URL must keep returning the old bytes, null-means-open-ended included.
- Never widen an existing field's meaning. If 'termination unknown' now needs representing, it cannot be null, because every deployed client already reads null as open-ended; it needs a new, explicitly enumerated field present only in the new shape. Redefining a value in place is the change that alters every client's behaviour without a single one being redeployed.
- Run the deprecation on evidence rather than a calendar. Instrument per-client usage of the old resource, serve Deprecation and Sunset headers with a Link to the successor, and gate each checkpoint - announce, stop new clients, contact the remaining callers by name, freeze, remove - on that metric. State what an unmigrated client gets at sunset: an explicit, documented error, never a silently different shape.
Worked solution (40 min)
- Write the old response and the new response side by side and mark every field that was added, removed, or had its meaning changed.
- For each marked field write the concrete failure a deployed client suffers - parse error, wrong decision, field dropped on round-trip - and the client behaviour that is its precondition.
- Write the projection rule that derives the old shape from the bitemporal store, then test it on a person with a retroactive termination and confirm the old bytes are unchanged.
- Choose the version carrier, write request and response for both resources, and state each one's cache key including both time axes.
- Write the deprecation timeline with its headers, the per-client usage metric gating each checkpoint, and the response an unmigrated client receives at sunset.
Expected result: The old path is preserved byte-compatibly, serving the projection as_of = today, asserted_at = now. A new resource accepts as_of (business date) and asserted_at (system instant) and returns entries with effective_date, termination_date, an explicitly enumerated termination certainty, coverage_order and the asserted-at window; null termination_date still means open-ended in both shapes. The old path carries Deprecation and Sunset headers with a Link to the successor. Removal is gated on per-client usage reaching zero across announce, freeze-new and direct-contact checkpoints, and an unmigrated client receives a documented 410 at sunset rather than a changed shape.
Verify it yourself
- A deployed client's bytes are unchanged both for a person with no retroactive corrections and for one with them.
- No existing field's set of legal values grew.
- The as-of read's cache key carries both time axes, so two different questions cannot share an entry.
- Sunset is gated on an observed metric, and an unmigrated client gets an explicit error rather than a different shape.
Follow-up
- An old client passes a filter parameter you only added in the new version. What do you return?
- A caching layer sits in front of both resources. What must be in the cache key for an as-of read, and what breaks if it is not?
- How would you prove no client round-trips the object back to you, before you rely on additive evolution?
Evening eligibility denials cluster in western time zones
Easy · timezones · bitemporal data · date boundaries
Eligibility denials rose from 0.4% to 3.1% of responses. They cluster after 17:00 local time, only at facilities in UTC-5 through UTC-10, and spike on the first and last day of each month. coverage_span.effective_date and termination_date are date columns in business time; the service derives the service date from encounter.admit_ts, a timestamptz. The enrolment files for the affected members look correct on inspection. Give the ordered diagnosis and the fix.
Approach
- Pivot the denial rate two ways before reading code: by facility UTC offset and by local hour of day. A defect that tracks local hour and offset is a time-conversion bug; one that tracks a source file, payer or plan is a data bug. The month-boundary spike is a consequence rather than a second problem, because terminations cluster on month ends and a one-day skew is most visible there.
- Take one denied request and recompute it by hand: the raw admit_ts, its UTC calendar date, the facility-local calendar date under the facility's IANA zone, and the coverage row's effective_date and termination_date. If the answer flips between the UTC date and the local date, the conversion is the cause and no further hypotheses are needed.
- Find the cast. In PostgreSQL, timestamptz::date is evaluated in the session's TimeZone setting, so the identical SQL returns different dates on different pooled connections and a pool that inherits UTC yields the UTC calendar date. date columns carry no zone at all, so comparing a date column to a UTC-derived date compares two different calendars.
- Fix once, at the edge: resolve the service date as (admit_ts AT TIME ZONE facility.iana_zone)::date and pass it down as an explicit date parameter. Use the IANA zone name, never a stored numeric offset, because the offset changes twice a year under daylight saving and a stored -8 is wrong for roughly eight months.
- Forbid re-derivation below the edge and pin the boundary with a frozen-clock test: an encounter at 23:30 local at a UTC-10 facility on the last day of a month whose coverage terminates that day, plus the same encounter one minute later. A test that calls now() passes for most of the day and hides the defect.
Follow-up
- Where else does the as-of date enter the system — the eligibility cache key, claim_line.service_from_date, the accumulator's plan year? Which of those are already computed in UTC?
- An inpatient encounter spans midnight local. Which date governs eligibility — the admit date or the service line date — and at which layer is that decided?
Suggested practice checklist
Roughly ninety minutes on weeknights with one longer weekend block. The plan cuts scope rather than compressing everything, on the assumption that one thing finished per night beats four half-started.
Day 1 — Fix the scope and take a cold baseline
- Read the role description and write the three things the loop will almost certainly test, then write an explicit not-doing list and keep it visible all week.
- Take one twenty-five-minute coding problem and one fifteen-minute design prompt cold, and write the single sentence naming what blocked each, because those two sentences decide where the remaining evenings go.
- Set the week's rule: one thing finished every night, including the night you only have forty minutes.
Deliverable: A one-page scope with a not-doing list and two cold attempts, each carrying one sentence on what blocked it.
Day 2 — One pattern, written three times from blank
- Choose the single pattern most likely to appear in your loop and write it three times from an empty file rather than editing the previous attempt.
- On the third pass, write the invariant as a comment before the loop body and the complexity before the first line of code.
- Stop at ninety minutes even if the third version is imperfect, and write the one thing you would fix given another hour.
Deliverable: Three independent implementations of the same pattern plus a note on what changed between them.
Day 3 — One design, only to the depth you can defend
- Take one system shape and go only as far as requirements, interface and data model, refusing to draw a box you could not survive a follow-up about.
- Attach one number to each non-functional requirement, deriving it rather than asserting it, and write the assumption the number rests on.
- Write the one tradeoff you are choosing against and the observation that would make you reverse it.
Deliverable: One design at interface-and-schema depth with derived numbers and one written reversible tradeoff.
Day 4 — Only the fundamentals you will have to defend
- Write, in under two hundred words each, the answers to the two questions that follow almost any implementation: why this structure and not the obvious alternative, and what happens to this code at a hundred times the input.
- Write what an index actually costs: faster lookups on the indexed columns against a write that now maintains a second structure, plus the cases where the planner declines to use it anyway, low selectivity, or a predicate wrapping the column in a function.
- Delete any answer you cannot deliver aloud in under a minute, since an answer that needs reading is not an answer you have.
Deliverable: Three written answers, each under two hundred words and each timed aloud.
Day 5 — Your own work, timed
- Write a ninety-second and a four-minute version of your main project and time both aloud rather than reading them.
- Prepare the two follow-ups that always come: what you would do differently, and how you knew it worked.
- Put one number in the first sentence and be ready to say exactly where it came from and what it excludes.
Deliverable: Two timed narratives with one defensible number in the opening line.
Day 6 — The one full rehearsal, in the weekend block
- Run a sixty-minute mock covering a coding round and a design round in one sitting with no break, because sustained attention is the thing evenings have not trained.
- Immediately afterwards, and before hearing any feedback, write the three moments you lost the thread.
- Spend the rest of the block only on those three moments, and on nothing you merely feel shaky about.
Deliverable: Mock notes naming three failure moments with a specific fix written under each.
Day 7 — Taper
- Write the twenty-minute warm-up you will actually do on the morning: one problem you can already solve from a blank file, one design you can narrate, and nothing you have never seen.
- Re-read only your own notes from this week and open no new material.
- Write the logistics down: the editor or shared document you will be working in, whether execution and lookups are permitted, and the sentence you will use when you do not know something.
Deliverable: A one-page card holding the design structure, the project numbers, and the logistics.
What the behavioural round is actually measuring
Every story you tell gets read for blast radius and judgement: what could have broken, who else it touched, what you knew at the moment you decided. Nobody can audit your code in an hour, so they audit your reasoning instead. Pick work where the call was genuinely yours and the consequences were real enough to remember.
Tell me about a project where you collaborated closely with hardware or field service teams.
Medium · behavioural and engineering judgement
Approach
- Close with what you would do differently, concretely.
- State the situation in two sentences and spend the rest on the reasoning.
- Pick a story where you made the decision, not one where you watched it.
Follow-up
- How did you know your change caused the improvement?
- What would you do differently if you ran that again?
How do you handle performance optimization when working with resource-constrained systems?
Medium · behavioural and engineering judgement
Approach
- Give the blast radius: what could have broken, and what you measured.
- Close with what you would do differently, concretely.
- Name the disagreement and how you resolved it with evidence.
Follow-up
- How did you know your change caused the improvement?
- What would you do differently if you ran that again?
Argue against rewriting foreign keys during a person merge
Hard · merge and unmerge · schema design · technical disagreement · read path
A proposal on your team simplifies merges: on merge, UPDATE enterprise_person_id across encounter, observation_result and claim_line to the surviving identity, then delete the link-resolution step so a patient-scoped read becomes one equality predicate. The author brings a p99 improvement on the patient summary, which fans out across eight to twelve resource types. You argued against it. Reconstruct the argument: the failure you predicted, the evidence you brought, the cost of your own alternative that you conceded, and who made the call.
Approach
- The probe is whether you can oppose a real performance win without retreating to data-hygiene arguments. Name the operation the proposal removes: unmerge. Once enterprise_person_id is rewritten in place, the row no longer records which source identity asserted the fact, and the only pair of columns that could — assigning_authority and source_person_id — lives in the link table the proposal deletes.
- Make it concrete with money rather than with charts. A claim_line posted under the losing identity carries coverage_id pointing at that identity's enrolment span. Rewriting the person while leaving coverage_id yields a row asserting the surviving person was covered under a span that was never theirs, which is a financial statement that adjudication and reconciliation will both act on.
- Do not argue that merges are rare, because that concedes the premise. Bring the rate of merges, the rejection rate on manually reviewed links, and the lag between a wrong merge and its discovery — false merges are a property of probabilistic matching, so the design has to assume them rather than hope against them.
- Concede the read cost honestly, since that is where the proposal's benefit is. Resolution through a versioned link table turns one equality predicate into a lookup feeding a predicate over a set of source identities, which changes index shape and raises the composite read's p99, and the composite is the number that matters, not the per-resource one.
- Offer the option that captures most of the win: a materialised current-link projection refreshed inside the merge transaction, so reads stay one predicate while the versioned link table remains authoritative and unmerge stays a supported operation rather than a recovery exercise.
- Finish on the decision process, not the technical point: what you wrote down, whether you blocked the change, who owned the call, and what you did once it was made — including if it went against you.
Follow-up
- Your projection and the link table disagree after a failed merge retry. Which one is authoritative, and how does a reader find out?
- An unmerge lands eight months later. What has to happen to the claims adjudicated under the merged identity in between?
- The author says reversibility can be handled by a nightly backup restore. Take that seriously and say exactly what it does not recover.