cswork / libraryOpen practice workspace

Interview guides · Evernorth Health Services India

Evernorth Health Services India Data Scientist Interview Questions & Guide 2026

Metric-diagnosis practice for Evernorth Health Services India data scientist candidates: a number moves week over week and you work out why, splitting by segment, ruling out a logging or release change, and separating a mix shift from a real change in user behaviour before naming a cause.

The 60-second brief

A Data Scientist at Evernorth Health Services India sits at the intersection of advanced analytics, healthcare innovation, and large-scale data systems. You are responsible for transforming complex healthcare datasets into actionable insights that improve patient outcomes, optimize operational efficiency, and drive strategic decision-making across the enterprise. Your work directly influences how Evernorth Health Services India approaches population health, pharmacy benefits, and clinical care delivery.

In modelling rounds the live question is usually why this model class for this problem, and how you would know six weeks after launch that it is still working. Deriving gradients by hand is rarely what is being probed.

Focus areas

  • Minimise protected health information in every extract
  • Reconstruct drug exposure intervals from dispensing records
  • Correct for claims runout before reporting recent months

Overview

This role is critical because the problems you tackle—such as predictive modeling for patient risk, health trend forecasting, and behavioral analysis—have a direct, measurable impact on the health and financial well-being of millions. You will collaborate with cross-functional teams including software engineers, clinical experts, and product managers to build robust, scalable machine learning models. Success in this role requires not only technical proficiency but also the ability to communicate complex findings to stakeholders who may not have a technical background.

The Evernorth Health Services India interview process

Evernorth Health Services India candidates report 3 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.

Recruiter Screen

Whoever runs this call is usually not a practitioner. They take notes, and a hiring manager skims those notes later, so the real question is whether your work survives being written down by someone outside the field. Test every project sentence against that: could a non-specialist repeat it correctly without knowing what a propensity score is? Carry a plain-language version of each project and one reason you want this particular role that you could not copy onto another application. Vagueness at this stage reads as inexperience, even when the underlying work was genuinely deep.

What to demonstrate

  • Whether a non-specialist can restate your projects accurately, since their paraphrase is what reaches the hiring manager
  • Whether your reason for wanting the role points at the work itself rather than the company's reputation
  • Whether your language signals the level being screened for: what you decided yourself versus what you were handed

How to prepare

  • Write a two-sentence, jargon-free version of each major project: the question nobody could answer, and the decision your work changed. Read it to someone outside data and have them repeat it back
  • Point your 'why this role' answer at something concrete in the job description or the product surface you would be working on, and keep it to two sentences
  • Have two questions ready about measurement: which metric the team is held to, and who acts on an analysis once it lands

Technical Deep-Dive

Much of what gets scored here happens out loud while you type. Nobody can see your reasoning inside a half-written query, so five silent minutes read as being stuck even when they are not. State the plan in plain language first: which tables, what grain you are aggregating to, and the one filter that defines the population. Then write it. The narration doubles as insurance, because a wrong plan gets caught early and cheaply while a wrong query gets caught at the end with no time left to redo it. A timed statistics section, where one exists, is a separate test with its own clock.

What to demonstrate

  • Whether the query you write matches the plan you just described
  • What you do with a hint, meaning whether the correction gets absorbed or the first approach gets defended
  • Whether you can debug your own wrong output by reading the result set and naming which part of the query produced the anomaly

How to prepare

  • Solve three problems while screen-sharing into a recording, then watch it back and mark every stretch longer than thirty seconds where you said nothing
  • Practise compressing the plan into one sentence before typing, then check afterwards whether the finished query actually matched it
  • Time yourself on statistics questions that carry a business reading, such as what a confidence interval does and does not claim, rather than re-reading notes without a clock

Team Interview

An added round often puts you in front of someone outside the core hiring team: a partner engineer, a product owner, a domain expert, sometimes a more senior manager. The question they are really asking is not whether you can do the work but whether they would trust a number that came from you. That changes what a good answer looks like. Lead with what the decision cost and what it changed, keep the method available but not central, and be plain about the limits of your evidence. Overstating a result is the fastest way to lose this round.

What to demonstrate

  • Whether you can explain a technical choice to someone who will never read your code, without either flattening it into nothing or hiding inside jargon
  • Honesty about evidence strength: what the analysis establishes, what it only suggests, and what it cannot say at all
  • How you take disagreement, specifically whether you update on a good objection, hold your position with reasons, or fold on contact

How to prepare

  • Write the two-sentence version of your most technical project for a non-specialist, then check that neither sentence needs a method name to make sense.
  • For one result you are proud of, write the strongest objection someone could raise and a response that concedes the part of it that is correct.
  • Prepare one decision that turned out to be wrong: how you found out, what it cost, and what you changed afterwards. A senior cross-functional interviewer asks for this more often than a technical one does.

Where candidates lose points

Rates built on member counts rather than exposure

Members join and leave mid-period, so dividing events by distinct members mixes a person covered for 30 days with one covered for 365. New joiners also have artificially low observed utilisation because their claims have not arrived yet and because care takes time to initiate. Denominators must be member-months or member-years, and comparative quality measures usually need a continuous-enrolment requirement with an explicit allowable gap, stated in days.

Reading the most recent months of a claims-based series as real

Claims incur before they are reported and paid, so recent incurred months are systematically undercounted until runout completes. The lag is not uniform: pharmacy adjudicates in days, professional claims in weeks, inpatient facility claims in months. That means recent data is both too low and mix-shifted toward cheap services, which reads as a cost improvement and a utilisation drop at once. The fix is to hold the last three incurred months back or apply completion factors, and to state the paid-through date on every chart.

Reporting a mean for a heavy-tailed metric without saying what it hides

For spend, session length or items per order, a small fraction of units carries most of the total, so the mean has a wide standard error and one account can move it. Fix the handling before you see the result: cap or winsorise at a pre-declared percentile, and report the median or the share above a threshold next to the mean. Capping changes the estimand, so say which question the capped number answers, and check how much of any difference comes from the top 0.1 percent of units.

Writing SQL without stating NULL and tie-breaking behaviour

Before calling a query finished, say what it does with NULLs, ties and empty groups. NOT IN against a subquery containing a single NULL returns no rows at all, and RANK, DENSE_RANK and ROW_NUMBER differ precisely on ties, so name which one the question requires.

Questions & practice

Explain the difference between bagging and boosting algorithms.

Medium · machine learning and modelling

Approach

  1. Frame the prediction: the label, the moment of prediction, and the action it triggers.
  2. Set a baseline first, so any model has something honest to beat.
  3. Check what information would not exist at prediction time, and exclude it.

Follow-up

  • What would you monitor after launch to know the model is still valid?
  • Where could label leakage enter this setup?

How do you evaluate the performance of a model beyond simple accuracy?

Medium · machine learning and modelling

Approach

  1. Frame the prediction: the label, the moment of prediction, and the action it triggers.
  2. Set a baseline first, so any model has something honest to beat.
  3. Say how the offline result would be validated online before it is trusted.

Follow-up

  • How would you choose the decision threshold, and who owns that choice?
  • What would you monitor after launch to know the model is still valid?

Collapse claim versions before summing allowed amounts by member

Easy · claim versioning · groupby · deduplication

You are given medical_claim_line as a pandas DataFrame: claim_line_id, claim_id, claim_version, frequency_code (1 original, 7 replacement, 8 void), member_id, service_start_date, procedure_code, allowed_amount, claim_status. Return total allowed_amount per member for service dates in one stated calendar month, counting each claim once at its surviving version and excluding any claim whose surviving version is a void. Multi-line claims must keep every line of that surviving version. Output columns: member_id, allowed_amount, sorted descending. Do not deduplicate on claim_id alone.

Approach

  1. Separate the two grains out loud before writing code: versions live at the claim level, dollars live at the line level. Every bug in this exercise comes from mixing them.
  2. Compute the surviving version per claim with a groupby transform of max over claim_version, then keep the lines whose claim_version equals that value. A transform keeps the frame at line grain, which a sort plus head(1) does not.
  3. Drop whole claims whose surviving version carries frequency_code 8. A void is not a zero-dollar line, it retracts the claim, so filtering rows rather than claims leaves the prior version's lines behind.
  4. Filter to claim_status 'paid' and state the assumption: denied, pended and reversed lines carry an allowed_amount that never became a liability, so including them overstates cost.
  5. Restrict service_start_date to the month boundaries, then groupby member_id and sum. Sort descending and return.

Worked solution (20 min)

  1. Build a max-version lookup per claim_id with groupby('claim_id')['claim_version'].transform('max') and keep rows where claim_version equals it.
  2. From the surviving rows, collect the claim_ids whose frequency_code is 8 and drop those claim_ids entirely.
  3. Filter claim_status to 'paid' and service_start_date into the month window.
  4. Group by member_id, sum allowed_amount, sort descending, reset the index.

Expected result: A two-column frame member_id, allowed_amount. On a claim with versions 1, 7, 7 only the third version's lines contribute; a claim whose latest version is frequency_code 8 appears nowhere. Total allowed is strictly below a naive sum over all rows whenever any claim in the sample was adjusted, and equal to it when none was.

Verify it yourself

  • The distinct claim_id count in your filtered frame equals the count of distinct claim_ids whose surviving version is not a void.
  • The line count you kept for each surviving claim equals that claim's line count at its max version, not 1.
  • A naive sum over every row is greater than or equal to your total, with equality only when no claim has claim_version above 1.

Follow-up

  • A claim has versions 1, 7, 7 where the second replacement has fewer lines than the first. What does your code return and is that correct?
  • How would you handle a claim whose lines span two calendar months at the boundary of the reporting window?
  • The same logic has to run over 400 million lines in a warehouse. What changes, and what stays the same?

How would you handle null values or missing data points in your SQL transformations?

Medium · sql and data manipulation

Approach

  1. Handle the rows that do not match: a LEFT JOIN with a NULL check is usually the question.
  2. State the window function and its partition and ordering out loud before writing it.
  3. Check whether any join is one-to-many before aggregating, or the sums inflate.

Follow-up

  • How does the query change if the join becomes one-to-many?
  • What breaks if events arrive late or out of order?

Explain the difference between various types of SQL joins and when to use them.

Medium · sql and data manipulation

Approach

  1. Handle the rows that do not match: a LEFT JOIN with a NULL check is usually the question.
  2. Say which table is the grain you start from, and join outward from it.
  3. State the window function and its partition and ordering out loud before writing it.

Follow-up

  • What breaks if events arrive late or out of order?
  • How does the query change if the join becomes one-to-many?

Write a SQL query to identify top-performing providers based on specific patient outcome metrics.

Medium · sql and data manipulation

Approach

  1. Handle the rows that do not match: a LEFT JOIN with a NULL check is usually the question.
  2. State the window function and its partition and ordering out loud before writing it.
  3. Check whether any join is one-to-many before aggregating, or the sums inflate.

Follow-up

  • What breaks if events arrive late or out of order?
  • How would you verify this result without re-running the same query?

How do you optimize a query that is performing slowly on a large database?

Medium · sql and data manipulation

Approach

  1. Say which table is the grain you start from, and join outward from it.
  2. State the window function and its partition and ordering out loud before writing it.
  3. Check whether any join is one-to-many before aggregating, or the sums inflate.

Follow-up

  • How would you verify this result without re-running the same query?
  • What breaks if events arrive late or out of order?

Deduplicate claim versions before totalling allowed amounts

Easy · dedup · window functions · claim versions

medical_claim_line holds one row per service line per claim version: claim_line_id, claim_id, claim_version, frequency_code (1 original, 7 replacement, 8 void), member_id, service_start_date, procedure_code, allowed_amount, claim_status and adjudicated_at. Adjusted claims appear more than once, so summing every row double counts them. Return total allowed_amount by procedure_code for service_start_date in Q1 2025, counting only lines with claim_status 'paid' on the surviving version of each claim, which is the highest claim_version per claim_id, and dropping the claim entirely when that surviving version carries frequency_code 8. Resolve survivorship with a window function, not a self-join.

Approach

  1. Resolve survivorship at claim_id, not claim_line_id. Carry MAX(claim_version) OVER (PARTITION BY claim_id) alongside every line and keep lines where claim_version equals it, because a replacement version can contain a different number of lines than the original.
  2. Read the frequency_code of the surviving version only. A frequency_code 8 removes the claim outright; it does not revert payment to the prior version, so a void must delete the claim rather than promote version n-1.
  3. Apply claim_status and the date filter after survivorship is resolved. Filtering paid lines first can strip the surviving version and silently elect a superseded one.
  4. Group by procedure_code and sum allowed_amount, then state the paid-through date on the output because Q1 amounts keep moving until adjudication runout completes.
  5. Sanity check the shrinkage: report how many claim_ids and how many dollars the dedup removed, so the reviewer can see the step did something.

Worked solution (20 min)

  1. Build a CTE that adds surviving_version = MAX(claim_version) OVER (PARTITION BY claim_id) and surviving_freq = the frequency_code of the row at that version, to every line.
  2. Filter to claim_version = surviving_version so all lines of the surviving version survive together.
  3. Drop claims whose surviving_freq is 8, then apply claim_status = 'paid' and service_start_date between 2025-01-01 and 2025-03-31.
  4. Group by procedure_code, sum allowed_amount, and emit the count of distinct claim_ids contributing.
  5. Run the naive SUM over all rows beside it and report the difference as the double count that was removed.

Expected result: One row per procedure_code with summed allowed_amount over surviving, non-voided, paid Q1 2025 lines. A claim with an original and one replacement contributes only the replacement's lines; a claim whose surviving version is a void contributes nothing.

Verify it yourself

  • Deduped total is strictly below the naive paid-and-Q1 SUM taken across all versions whenever at least one claim carries paid Q1 lines on more than one version. If the two totals match on such a claim, the dedup did nothing.
  • For one claim_id known to have three versions, the line count it contributes equals the number of its paid Q1 2025 lines at its highest claim_version, not 1.
  • Let B be the set of claim_ids whose surviving version has at least one paid line with a Q1 2025 service_start_date. COUNT(DISTINCT claim_id) in the output equals the size of B minus the number of claims in B whose surviving version carries frequency_code 8. Comparing instead against COUNT(DISTINCT claim_id) over the whole raw table is not a void test: that gap also absorbs every claim dropped by claim_status or by service date.

Follow-up

  • Two rows share a claim_id and claim_version but differ on adjudicated_at. What do you do, and what does that imply about the extract?
  • How does the query change if the request is allowed_amount by paid_date rather than service_start_date, and which basis does a finance reconciliation want?
  • A void arrives for a claim already published in a closed month. How do you restate without rewriting history in the warehouse?

Reduce variance on a skewed cost outcome with CUPED

Medium · cuped · variance reduction · stratification · skewed outcomes

A member-randomised digital care programme will be read on 12-month allowed PMPM, built from surviving medical_claim_line versions plus unreversed pharmacy_claim rows over member-months from member_enrollment. The outcome is heavily right-skewed. Eighty percent of members have 12 months of prior continuous coverage and therefore a usable pre-period PMPM; among those, the correlation between pre-period and post-period member PMPM is 0.45. Propose a variance reduction plan, quantify the expected gain, and say how you handle the 20 percent with no pre-period.

Approach

  1. Apply CUPED with the pre-period PMPM as the covariate: theta = Cov(Y, X) / Var(X), and Y_adj = Y - theta x (X - mean(X)). Because X is measured strictly before randomisation it cannot be affected by treatment, so the adjusted difference in means stays unbiased while its variance falls by the factor 1 - rho^2.
  2. Quantify honestly: rho = 0.45 gives 1 - 0.2025 = 0.80, a 20 percent variance reduction inside the covered stratum, which is a real but modest gain. Do not quote consumer-experimentation figures of 40 to 50 percent, which come from outcomes far more autocorrelated than health spend.
  3. Handle the skew before handling the covariate. Pre-specify a truncation point (a percentile of the prior year, or a fixed stop-loss amount) applied identically to both arms and fixed before unblinding, and state plainly that you are now estimating an effect on truncated cost. Report the untruncated result as a secondary so nobody can claim the cap created the finding.
  4. Do not impute a mean pre-period for the 20 percent with none. Stratify: run CUPED inside the covered stratum, take the unadjusted difference in the no-pre-period stratum, and combine with fixed weights; equivalently, regress with a missingness indicator and an indicator-by-covariate interaction. Both keep the estimator unbiased because the stratum is defined pre-randomisation.
  5. Get some of the same gain for free at assignment time by stratifying or covariate-constrained randomisation on prior-year cost decile, which removes the post-hoc analytic choices that a sceptical reviewer will otherwise interrogate.

Follow-up

  • rho drops to 0.20 after truncation. What do you do with the CUPED plan?
  • Members with no pre-period are new enrollees. What does that do to your assumption of equal per-member variance across strata?
  • Would you switch the estimand from mean PMPM to a rank-based or log-scale comparison, and what would that cost you with a finance audience?

Specify a readmission rate two parties will settle on

Easy · measure specification · exclusions · observed over expected · runout

A payer and a hospital group are writing a shared-savings term that settles on 30-day unplanned readmissions. You have encounter (encounter_id, patient_id, member_id, facility_id, encounter_type, admit_ts, discharge_ts, admission_type, discharge_disposition, is_planned_admission, index_encounter_id, drg_code, principal_diagnosis_code) and member_enrollment coverage spans. Produce the measure specification both sides sign: numerator, denominator, exclusion list, observation window, and the paid-through date at which a discharge month becomes reportable. State whether settlement is on the raw rate or an observed-over-expected ratio, and defend the choice.

Approach

  1. Fix the unit of analysis first: the denominator is eligible index stays, not members and not patients. Say out loud that one member can contribute several index stays in a period, and that a readmission is itself eligible to become an index stay, because leaving that unstated is where two implementations diverge.
  2. Write the exclusion list as a waterfall with a count at each step rather than a sentence: planned admissions (is_planned_admission TRUE), discharge_disposition in expired, hospice and ama, and stays whose discharge_disposition is transfer_acute, which are the first leg of one episode rather than an index stay that ended.
  3. Add the exposure condition the encounter table cannot express on its own: an index stay only counts if the member has coverage in member_enrollment for the full 30 days after discharge_ts, with the allowable gap written in days. Without it, a disenrollment reads as a clean outcome.
  4. Define the numerator join precisely: the earliest later inpatient encounter with is_planned_admission FALSE and admit_ts within 30 days of the index discharge_ts, at any facility_id, capped at one readmission per index stay so a single unstable member cannot dominate a facility's rate.
  5. Set the maturity rule. Inpatient facility claims adjudicate slowest of any service type, so name a paid-through lag, put the paid-through date on the report header, and state that a discharge month is restated until it matures.
  6. Settle on observed-over-expected and say why: raw rates confound case mix with care, and the expected model is fitted on age, sex and comorbidities drawn from the 12 months of claims before admit_ts, excluding diagnoses first coded during the index stay so complications of that stay do not inflate the facility's own expectation.

Worked solution (25 min)

  1. Write the eligible-index waterfall as a numbered list before touching any logic, with a placeholder count beside each line so the reviewer can see how much of the raw discharge volume each exclusion removes.
  2. Write the numerator rule in one sentence including the tie-break for two admissions on the same day, and the one-readmission-per-index cap.
  3. State the coverage requirement as a concrete filter: covered on every day from discharge_ts to discharge_ts + 30, allowable gap 0 days, evaluated against member_enrollment spans rather than a single active flag.
  4. Name the paid-through lag and write the restatement policy: a discharge month is provisional until it matures and is republished at every refresh until then.
  5. Write two sentences on why settlement is on O/E, including what risk adjustment does not fix, namely unrecorded severity and differences in social support at discharge.

Expected result: A one-page specification containing: an exclusion waterfall with a count at every step; a numerator counting at most one unplanned acute readmission per index stay within 30 days of discharge_ts at any facility; a 30-day continuous-coverage requirement with the allowable gap stated in days; a named paid-through lag with a restatement policy; and settlement on the observed-over-expected ratio with a confidence interval, on the stated ground that raw rates are not comparable across facilities with different case mix.

Verify it yourself

  • Recompute the raw rate with and without the transfer_acute exclusion. A facility that receives transfers should move materially; if nothing moves, the transfer legs are not being detected at all.
  • Compare the numerator count to the count of distinct encounters serving as a readmission. They should be equal; a larger numerator means one readmission is being credited to two index stays.
  • Re-run the same discharge month at two paid-through dates 60 days apart. A stable rate means the lag is long enough; a rate that climbs means it is not.
  • Confirm every excluded stay lands in exactly one exclusion bucket so the waterfall sums back to the raw discharge count.

Follow-up

  • A member transfers from one acute facility to another on day two of the stay. One episode or two, and which facility owns the outcome?
  • The hospital starts admitting borderline patients to observation status instead of inpatient. What happens to your numerator, your denominator and the rate, and which companion metric would show it?
  • Why would you refuse to report this rate for a facility with 40 eligible index stays in a quarter?

Pooled persistency fell but no one lost coverage

Medium · interval sql · mix shift · cohort seasonality

Pooled 12-month enrollment persistency fell from 0.78 to 0.66 for the cohort anchored in a single month. member_enrollment carries span_id, member_id, plan_id, product_type, effective_date, termination_date, termination_reason_code, dual_eligible_flag and region_code. The metric allows at most one gap of 45 days or fewer. Product mix in that anchor month was not stable. Determine how much of the fall is real disenrollment rather than span bookkeeping or mix, and deliver a persistency series a contract owner could act on.

Approach

  1. Inspect the query's unit of analysis first. Spans are not members, and a benefit or plan change closes one span and opens the next on the same day, so any logic that treats a non-null termination_date as loss of coverage counts an administrative event as churn.
  2. Rebuild member-level coverage by unioning all spans per member and merging adjacent or overlapping ones, then apply the 45-day gap rule to the merged intervals rather than per span.
  3. Cut the terminations by termination_reason_code. plan_change and eligibility_redetermination have entirely different meanings for a contract, and death is not churn you can address.
  4. Split by product_type and stop pooling. Commercial open enrollment and managed medicaid redetermination produce structurally different churn on different calendars, and the pooled average of the two is a number that describes nobody.
  5. Compare the anchor month to the same month in the prior year rather than to the adjacent month, because enrollment is strongly seasonal and an anchor month sitting on an open-enrollment or redetermination boundary is not comparable to its neighbour.
  6. Apply the Kitagawa decomposition across product_type to separate how much of the 12 point fall is mix, meaning the anchor cohort simply contained more of a high-churn product, from how much is within-product deterioration.

Follow-up

  • Why 45 days, and what would change if the allowable gap were 30 or 60?
  • A member terminates and re-enrolls 90 days later under a different plan_id. Are they retained? Defend your answer against the opposite one.
  • What guardrail would catch someone improving persistency by declining to enroll members likely to churn?

Suggested practice checklist

For someone who has spent the last year in notebooks, dashboards or modelling work and has not written raw SQL under time pressure. The first four days rebuild query fluency against a fixture you control and can verify by hand; the last three attach that fluency to the rest of the loop.

Day 1 — Build a fixture you can check answers against

  • Create a local Postgres or SQLite database with four tables (users, sessions, events, orders) holding roughly 200 rows you generated yourself, so you know the contents well enough to predict every result.
  • Deliberately seed the cases that break queries: a user with no sessions, a session with no events, two orders sharing a timestamp, a NULL in one join key, and one duplicated user row.
  • Before writing any SQL, hand-compute five answers on paper (how many users placed at least one order, median orders per ordering user, and three others) and save them as the ground truth for the week.

Deliverable: A one-command seed script plus a text file of five hand-computed answers to grade every later query against.

Day 2 — Joins, filters and NULL semantics

  • Answer "which users have no orders" three ways (LEFT JOIN with IS NULL, NOT EXISTS, NOT IN) and confirm that the NOT IN version returns zero rows once the subquery contains a NULL, because the comparison is never TRUE.
  • Reproduce the LEFT JOIN that silently collapses to an inner join by putting a right-table predicate in WHERE, then fix it by moving the predicate into the ON clause, and record both row counts.
  • Create a fan-out bug on purpose by joining orders to order_items and summing the order total, then correct it with a pre-aggregated subquery and explain in one line which table changed the grain.

Deliverable: One annotated .sql file holding the three join traps, each with the wrong result and the corrected result side by side.

Day 3 — Window functions and frames

  • Write three window queries against the fixture: a running order total per user, the rank of each order within its user by value, and the day gap to that user's previous order, then check each against the day-one ground truth.
  • Run ROW_NUMBER, RANK and DENSE_RANK over a column containing ties, print all three side by side, and write one sentence on when each is the correct choice.
  • Switch one query from the default frame (RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, which is what you get when ORDER BY is present and no frame is written) to ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, and explain why the output differs only when the ORDER BY column has duplicates.

Deliverable: Three verified window queries plus a short note explaining the RANGE versus ROWS difference in your own words.

Day 4 — The four analytical query patterns

  • Write a monthly retention grid: first order month per user, then months-since-first as the column, and verify that month zero equals the cohort size exactly.
  • Sessionize the events table under a 30-minute inactivity rule using LAG plus a cumulative sum over a new-session flag.
  • Build a four-step funnel that counts distinct users rather than events at each step, and state the rule you applied to a user who reaches step three without ever logging step two.

Deliverable: One file with the retention, sessionization and funnel patterns, each carrying a one-line note on the assumption it bakes in.

Day 5 — Write SQL the way you will have to write it live

  • Set a 12-minute timer and solve three medium prompts in a plain editor with no execution and no autocomplete, then run them and tally syntax errors separately from logic errors.
  • Narrate one solution aloud while writing it, stating the grain of each intermediate result (one row per user, one row per user-day) before you type its body.
  • Rewrite your slowest solution as a CTE chain where every CTE name states its grain, and time yourself re-solving it from blank.

Deliverable: A recording of one narrated solution plus an error tally that separates syntax from logic.

Day 6 — One day for everything that is not SQL

  • Write the preconditions of the two-sample t-test from memory, then check them: independent observations, and a difference in means whose sampling distribution is approximately normal, which at large sample sizes follows from the central limit theorem rather than from normality of the raw values.
  • Write the difference between an odds ratio from logistic regression and a relative risk, and state the condition under which the two are close (low outcome prevalence).
  • Prepare a 90-second answer to "how would you know this model is any good" that names the metric, the baseline you would beat, and the cost of the errors you care about.

Deliverable: One page of notes covering test preconditions, the odds-ratio caveat and the model-quality answer.

Day 7 — Full loop rehearsal

  • Run a 45-minute mock with someone willing to interrupt: 20 minutes of SQL, 15 minutes defining a metric, 10 minutes on a past project.
  • Re-solve from blank the two queries you were slowest on this week and compare the times against day five.
  • Write a five-line answer to "walk me through a project" that puts a number in the first sentence and names the decision the work changed.

Deliverable: Mock feedback notes plus a timed project narrative you can deliver without reading it.

Explaining a method to someone who will not follow the maths

Half of this section is about translation. Be ready to describe how you explained a result to someone who did not want the method, only the implication, and what you did when the simplified version started being repeated in a way that overstated it. Correcting your own simplification is a strong beat.

Describe a challenging project where you had to pivot your strategy based on stakeholder feedback.

Medium · behavioural and stakeholder questions

Approach

  1. State the situation in two sentences and spend the rest on your reasoning.
  2. Quantify the outcome, including what you would not claim credit for.
  3. Pick a story where you drove the decision, not one where you observed it.

Follow-up

  • What would you do differently if you ran that project again?
  • What did you decide not to do, and why?

How do you handle disagreements within your team regarding a technical approach?

Medium · behavioural and stakeholder questions

Approach

  1. Close with what you would do differently, concretely.
  2. Quantify the outcome, including what you would not claim credit for.
  3. Pick a story where you drove the decision, not one where you observed it.

Follow-up

  • What would you do differently if you ran that project again?
  • How did you know the outcome was caused by your change?

Push back on an over-broad extract without blocking the work

Hard · minimum necessary · de-identification · governance

A researcher asks for a one-off extract covering 12,000 members: member_id, date of birth, five-digit residential postal code, full lab_result rows including value_text, and clinical note text, held on their laptop for a retrospective analysis of one lab analyte over time. They describe it as de-identified because names are excluded. A flat refusal is not an acceptable answer. Deliverable: the column list you will actually provide with a justification per retained field, the sentence explaining why free text defeats an enumerated-identifier approach, your small-cell rule for the published stratification, and what you deliver this week.

Approach

  1. The probe is whether privacy is a design skill for you or a compliance reflex. Flat refusal and unquestioned fulfilment both fail, for opposite reasons.
  2. Reconstruct the analysis from the research question rather than from the request. A longitudinal trend in one analyte needs a stable pseudonymous subject key, the analyte's loinc_code, value_numeric, units, the reference bounds, and a date. It does not need names, notes, or a precise birth date.
  3. Replace each identifier with the least precise version that still answers the question: a salted per-study pseudonym instead of member_id, age in years or an age band instead of date of birth, a consistent per-subject date offset if only intervals matter, and a coarser geography than five-digit postal code.
  4. Explain the free-text problem in one sentence. Removing an enumerated list of identifier fields does not de-identify a note, because notes carry names, relationships, employers, dates and rare clinical detail in prose, so only a reviewed extraction into structured fields or a formal statistical determination addresses it.
  5. Set the publication rule before the analysis runs. Suppress cells below a stated threshold and suppress complements too, because a reader holding a row total and all but one cell recovers the suppressed count by subtraction.
  6. Keep them moving. Deliver the reduced extract into a controlled environment this week rather than onto a laptop, and offer to derive any note-based variable yourself as a structured flag so they never need the prose.

Follow-up

  • They say the age band loses resolution their model needs. What do you do?
  • How do you set the suppression threshold, and who signs off on it?
  • The analysis later needs linking to a second data source. What changes?