Interview guides · Assurant
Assurant Data Scientist Interview Questions & Guide 2026
A two-week prep plan for Assurant data scientist candidates: write your answer before reading the solution, revisit misses three days later.
The 60-second brief
As a Data Scientist at Assurant, you sit at the intersection of complex risk management and innovative technology. Assurant operates in a unique space—protecting the products that matter most to consumers, from mobile devices to home appliances and automotive assets. Your work directly influences how the company assesses risk, manages claims, and optimizes the customer experience across global markets.
Product-sense cases reward reasoning from a mechanism to a testable prediction. Reciting every metric you can name reads as pattern matching; naming the single quantity that would move if your explanation were true reads as thinking.
Focus areas
- Decompose expected loss into PD, LGD, EAD
- Report only matured cohorts for loss metrics
- Reconcile amounts in minor units and currency
Overview
This role is not just about building models; it is about translating massive, multi-faceted datasets into actionable business intelligence. You will collaborate with cross-functional teams, including product, engineering, and operations, to solve high-stakes problems. Whether you are improving predictive accuracy for warranty programs or identifying patterns in consumer behavior, your contributions have a tangible impact on the company’s bottom line and operational efficiency.
The role requires a strong balance of technical rigor and business acumen. Be prepared to explain not just the 'how' behind your models, but the 'why' in terms of business value.
The Assurant interview process
Assurant candidates report 2 rounds · ≈ 2-4 weeks. The stages below are what candidates describe, not a published process.
HR Screening
Data Scientist covers at least four different jobs: experimentation, product analytics, causal work on observational data, and applied modelling that ships into a system. A screening call is the cheapest place to find out which of them is being hired for, and doing that diagnosis openly reads as senior rather than fussy. Ask what the last few pieces of work on the team actually were, and roughly how a week splits between querying, modelling and stakeholder time. Then say which parts of that you have done and which you have not. Claiming the whole range is the fastest way to be caught one round later.
What to demonstrate
- Whether you can distinguish the flavours of the role and locate your own experience inside one of them honestly
- Whether you name what you have not done instead of stretching to cover every line of the posting
- Whether your hard constraints (notice period, location, work authorisation, level) surface now rather than at offer stage
How to prepare
- Map the last two years of your time into rough percentages across query writing, experiment design, modelling and stakeholder work, so a question about scope has a real answer
- Mark every responsibility in the posting as done, adjacent or new, and prepare one sentence for each adjacent item naming the closest thing you have actually built
- Decide which logistics are non-negotiable before the call so you can state them in one sentence rather than negotiating live
Technical Interviews
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
Where candidates lose points
Recalibrating an underwriting cutoff on approved and funded applicants only
Rejected applicants have no repayment outcome, and they were rejected because the incumbent model scored them badly, so the missingness depends directly on the outcome being modelled. Reject inference by augmentation or parcelling fills the gap using the incumbent model's own assumptions, which means it can confirm those assumptions but cannot test them. The only genuinely new information about the reject region comes from bureau performance on rejects who borrowed elsewhere, or from a deliberately randomised approval band around the cutoff.
Averaging delinquency across a book that is growing
A loan three months old cannot be 90 days past due, so a portfolio with many recent originations reports a low blended 90+ rate purely from age mix. The blended rate falls fastest exactly when originations grow fastest, which is precisely when credit quality most needs watching, so the metric moves in the reassuring direction during the riskiest period. Only comparisons at equal months on book are valid, which is what a vintage or roll-rate view enforces.
Crediting a treatment for regression to the mean
Selecting a group because it is extreme (lowest-engagement users, accounts having their worst month, the bottom decile of a score) moves that group's expected next-period value back toward the average even under no treatment, by exactly as much as the selecting measure is imperfectly correlated with its own later value. Compare against units that met the same selection rule and went untreated, or use two pre-periods so the bounce-back is visible before the intervention starts. A pre-post number on a group chosen for being extreme measures the selection rule, not the treatment.
Stopping an experiment the moment it crosses significance
Fix the sample size or duration before launch, or use a method built for continuous monitoring such as a sequential test, always-valid confidence intervals, or group-sequential boundaries. Repeatedly checking a fixed-horizon p-value against 0.05 pushes the real false-positive rate well above 5 percent.
Questions & practice
What are the common pitfalls when dealing with imbalanced datasets in fraud detection?
Medium · statistics and probability
Approach
- Quantify uncertainty explicitly rather than reporting a point estimate alone.
- Translate the result into the decision it informs, in one plain sentence.
- Write down the assumption the method needs before you use the method.
Follow-up
- What sample size would you need to detect an effect half this size?
- Which assumption here is most likely to be violated in practice?
How do you manage dependencies and environment variables in your machine learning projects?
Medium · machine learning and modelling
Approach
- Set a baseline first, so any model has something honest to beat.
- Frame the prediction: the label, the moment of prediction, and the action it triggers.
- Check what information would not exist at prediction time, and exclude it.
Follow-up
- Where could label leakage enter this setup?
- How would you choose the decision threshold, and who owns that choice?
How do you explain a complex machine learning model to a non-technical stakeholder?
Medium · machine learning and modelling
Approach
- Say how the offline result would be validated online before it is trusted.
- Set a baseline first, so any model has something honest to beat.
- Frame the prediction: the label, the moment of prediction, and the action it triggers.
Follow-up
- Where could label leakage enter this setup?
- What would you monitor after launch to know the model is still valid?
Estimate a delinquency roll-rate matrix and project twelve months
Hard · roll rates · markov chain · survivorship
fct_loan_performance_monthly gives loan_id, as_of_month_end, months_on_book, delinquency_bucket, charge_off_flag, prepaid_in_full_flag and restructured_flag. Build a month-to-month transition matrix over the five delinquency buckets plus absorbing charged_off and prepaid states. Loans that stop appearing must be routed to an absorbing state rather than dropped. Project the current book forward 12 months by repeated matrix multiplication and report the projected share reaching charge-off. Handle restructured_flag explicitly, and name one place the Markov assumption fails on this data.
Approach
- Build consecutive month pairs per loan by shifting as_of_month_end within loan_id, then verify the shifted value is exactly one month later. A gap is not a transition, it is an exit you have not resolved yet.
- Resolve exits before counting anything. A loan whose last row carries charge_off_flag moves to charged_off, one carrying prepaid_in_full_flag moves to prepaid, and one that disappears with neither is a data question to raise rather than silently discard, because discarding it is survivorship that inflates every cure rate.
- Count pairs into a 7 by 7 matrix and row-normalise. Assert every row sums to one and the two absorbing rows are the identity; a row that does not sum to one means exits were dropped.
- Decide and state the restructure rule. Restructuring resets days_past_due, so a dpd_60_89 to current move on a restructured loan is not a cure. Either give restructured loans their own state or carry the pre-restructure bucket, but do not let that move land in the cure cell.
- Project by taking the current bucket distribution as a row vector and multiplying by the matrix twelve times. Report the charged_off entry, and report it again from an all-current starting vector so the reader can see how much of the projection comes from loans that are already delinquent today.
- State the homogeneity failure plainly: transition rates depend strongly on months_on_book, so one pooled matrix applied to a book with a young mix understates early-life delinquency. If the mix is moving, estimate separate matrices by seasoning band.
Worked solution (45 min)
- Sort by loan_id and as_of_month_end, shift to form (from_state, to_state) pairs, and flag pairs whose month gap is not exactly one.
- For each loan's final row, assign the absorbing destination from charge_off_flag or prepaid_in_full_flag, and list loans that vanish with neither as an exception count to report.
- Apply the restructure rule, then build the 7 by 7 count matrix with a cross-tabulation over ordered state categories and row-normalise it.
- Assert row sums equal one and absorbing rows are the identity, then take the current month's bucket distribution as a row vector.
- Multiply twelve times, report the charged_off component, and repeat from an all-current vector for comparison.
Expected result: A 7 by 7 row-stochastic matrix with identity rows for charged_off and prepaid, a current-to-current diagonal typically above 0.95, roll rates rising with bucket depth, and a 12-month projected charge-off share of a few percentage points from an all-current start and materially higher from the actual book.
Verify it yourself
- Every row sums to one within floating-point tolerance, and both absorbing rows are exactly the identity.
- No mass sits in cells that skip a bucket upward, such as dpd_1_29 to dpd_60_89 in one month; if there is, the pairs are not actually consecutive months.
- The projection from an all-current start is strictly below the projection from the real book distribution.
- Backtest by estimating on months up to t minus 12 and comparing the projected distribution with the realised one at t; a systematic optimistic gap points at dropped exits or at seasoning, not at noise.
Follow-up
- How would you validate the projection against what actually happened, and over what window?
- The cure rate out of dpd_30_59 rose five points last quarter. What are the candidate explanations and how would you separate them?
- When would you prefer a vintage curve to a roll-rate projection, and why?
Describe your process for cleaning and preprocessing raw data using Python.
Medium · sql and data manipulation
Approach
- Check whether any join is one-to-many before aggregating, or the sums inflate.
- Say which table is the grain you start from, and join outward from it.
- State the window function and its partition and ordering out loud before writing it.
Follow-up
- How does the query change if the join becomes one-to-many?
- What breaks if events arrive late or out of order?
How would you optimize a slow-running SQL query involving millions of rows?
Medium · sql and data manipulation
Approach
- Say which table is the grain you start from, and join outward from it.
- Handle the rows that do not match: a LEFT JOIN with a NULL check is usually the question.
- 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?
Write a SQL query to join multiple tables and aggregate data based on specific time-based constraints.
Medium · sql and data manipulation
Approach
- State the window function and its partition and ordering out loud before writing it.
- Handle the rows that do not match: a LEFT JOIN with a NULL check is usually the question.
- Compute rates by summing numerator and denominator separately, never by averaging rates.
Follow-up
- How does the query change if the join becomes one-to-many?
- What breaks if events arrive late or out of order?
Vintage ninety-plus rate at twelve months on book
Medium · vintage analysis · window functions · right censoring
From fct_loan_performance_monthly, build a vintage table keyed on origination_month: the share of each cohort that ever reached days_past_due of 90 or more, or charge_off_flag = true, at or before months_on_book = 12. Restructuring resets days_past_due, so for any loan with restructured_flag true, evaluate only the month ends strictly before its first restructured month. Return origination_month, loans_funded, bad_loans and bad_rate. Exclude any cohort that does not yet have a months_on_book = 12 observation for every loan still on book.
Approach
- Establish the cohort denominator from the first month end each loan appears at, months_on_book = 0, so a loan is counted once in its origination_month rather than once per monthly row.
- Find each loan's first restructured month with MIN(as_of_month_end) FILTER (WHERE restructured_flag) OVER (PARTITION BY loan_id), or the equivalent grouped subquery, and keep it null for loans never restructured.
- Flag a loan bad if any row with months_on_book <= 12 and as_of_month_end earlier than that first restructured month has days_past_due >= 90 or charge_off_flag = true, which is what 'pre-restructure worst state' means in practice.
- Gate maturity by requiring the cohort's newest month end to be at least 12 months after origination_month, and report immature cohorts as incomplete rather than letting them appear at a flattering low rate.
- Aggregate to one row per origination_month and read the column downward, not across calendar time, because the whole point is comparing cohorts at equal age.
Worked solution (35 min)
- CTE 1: per loan_id, derive origination_month, the first restructured month end, and the maximum months_on_book observed.
- CTE 2: join back to the monthly rows, filter to months_on_book <= 12 and to month ends before any restructure, then flag bad with a BOOL_OR.
- CTE 3: aggregate to origination_month with COUNT() as loans_funded and COUNT() FILTER (WHERE is_bad) as bad_loans.
- Apply the maturity gate and compute bad_rate with a numeric cast.
Expected result: One row per matured origination_month with loans_funded, bad_loans and a bad_rate between 0 and 1. The most recent immature cohorts are absent from the output entirely rather than present with a small bad_rate.
Verify it yourself
- SUM(loans_funded) across reported cohorts equals COUNT(DISTINCT loan_id) restricted to those same origination months.
- Recomputing without the months_on_book cap raises bad_rate monotonically for every cohort, and raises it most for the oldest, which is the seasoning effect made visible.
- No cohort in the output has a maximum months_on_book below 12.
Follow-up
- Should a restructure inside 12 months count as bad in its own right? Argue both sides and say what you would actually ship.
- A loan that prepaid in full at month 4 never had a chance to go 90 days past due. In or out of the denominator, and why?
- The 2025-11 cohort is two points worse at month 12 than its neighbours. What three queries do you run before you call it a credit-quality change?
How do you prioritize tasks when working on multiple high-impact projects simultaneously?
Medium · product sense and metrics
Approach
- State what result would change your recommendation, so the answer is falsifiable.
- Fix the population and the time window before naming any metric.
- Decompose the metric into the rates that drive it, and say which one you would check first.
Follow-up
- How would you detect that the metric is being gamed rather than genuinely improving?
- Which segment would you cut first, and what would that rule out?
Read a segment-sliced result with a fading first-week lift
Medium · multiple comparisons · novelty effects · mix shift
A three-week test of an in-app credit-limit-increase offer had one pre-registered primary metric. The readout also slices 24 secondary segment-by-metric cells, of which three are significant at 0.05 with p-values 0.011, 0.028 and 0.041. The treatment effect on the primary was plus 8.1 percent in calendar week one, plus 3.4 percent in week two and plus 1.5 percent in week three. Say which claims survive, what the decay does and does not prove, and what you tell the sponsor.
Approach
- Compute what chance alone produces before interpreting any cell. With 24 tests at 0.05 the expected number of false positives is 1.2, the probability of at least one is 1 - 0.95^24 = 0.708, and the probability of three or more under an independence approximation is about 0.12. Three hits is an unremarkable outcome, not a signal.
- Apply a stated correction rather than a vibe, and be precise about which one the reported numbers can settle. Holm and Bonferroni both begin at 0.05 / 24 = 0.00208, so the smallest p-value alone decides them. Benjamini-Hochberg sorts all m p-values ascending and finds the largest k with p(k) <= k * q / m; at q = 0.10 and m = 24 the rank-k threshold k / 240 starts at 0.00417 and rises to 0.10 at k = 24, so it is a step-up test over the whole family and its verdict cannot be read off the three cells that happened to fall below 0.05. Ask for all 24 p-values, report the surviving set, which may be empty, and demote the rest to hypotheses for a future test.
- Refuse to call the decay novelty until the competing explanations are ruled out. Three things produce a falling weekly effect: a genuine novelty response from existing users that reverts, a mix shift because week three's exposed population is not week one's, and an effect that is correctly one-shot because a limit increase is taken once per customer.
- Separate them with concrete cuts. Plot the effect against time since first exposure instead of calendar week, which removes the mix shift. Compare users onboarded during the test, who never saw the previous experience and therefore cannot show novelty, against existing users. Hold back a small fraction of traffic for a long-run read.
- Fix the estimand to match the mechanism. For a one-shot offer the meaningful quantity is cumulative take-up per exposed user over a fixed horizon, not a weekly rate, and under that estimand a declining weekly rate is exactly what a working feature looks like.
- Give the sponsor one number with its interval, name the decision it supports, and list the exploratory cells separately under a heading that says they are not results.
Worked solution (25 min)
- Compute the chance baseline: 0.95^24 = 0.292, so P(at least one false positive) = 0.708 and the expected count is 1.2.
- Settle the corrections the reported numbers can settle. Holm and Bonferroni both require the smallest p-value to clear 0.05 / 24 = 0.00208; 0.011 does not, so neither rejects anything, and that conclusion needs only the one number. Benjamini-Hochberg is different: with m = 24 and q = 0.10 the rank-k threshold is k / 240, which runs 0.00417, 0.00833, 0.0125 for the three reported cells (all three fail their own thresholds) but passes 0.05 at k = 13 (0.05417) and reaches 0.10 at k = 24. Because BH rejects the k smallest hypotheses whenever any p(k) clears its threshold, one of the 21 unreported p-values sitting between 0.05 and its own rank threshold would make BH reject every cell down to rank 1, the three included. Demand the full ordered vector of 24 p-values before stating a BH verdict.
- Rebuild the weekly series on time since first exposure and overlay new versus existing users, then state which of novelty, mix shift and one-shot mechanics the pattern is consistent with.
- Recompute the primary as cumulative take-up per exposed user at a fixed 21-day horizon and report that with its confidence interval as the headline.
- Write the sponsor summary: one confirmatory number, the exploratory cells listed as non-results pending the full p-value vector, and a proposed follow-up test for the single most plausible segment hypothesis.
Expected result: Holm and Bonferroni reject nothing, and that follows from the reported numbers alone: the smallest p-value, 0.011, exceeds 0.05 / 24 = 0.00208. Benjamini-Hochberg at q = 0.10 is not decidable from three p-values, because it is a step-up test over all 24 whose rank-k threshold k / 240 passes 0.05 at k = 13, so the correct move is to demand the full ordered vector and name the condition that would flip it rather than assert that nothing survives. Until that vector arrives the three cells are exploratory, not findings. Only the pre-registered primary is confirmatory, restated as cumulative take-up over a fixed horizon. The 8.1 to 1.5 percent decay is not established as novelty; the correct answer names mix shift and the one-shot nature of a limit increase as competing explanations and gives the cuts that separate them.
Verify it yourself
- Binomial sanity check: P(3 or more hits out of 24 at 0.05) is about 0.12 under independence, so three hits is unremarkable; correlation among the cells makes the true probability higher still.
- BH self-check: the rank-k thresholds k * 0.10 / 24 must run from 0.00417 at k = 1 to 0.10 at k = 24 and cross 0.05 between k = 12 and k = 13. Any BH conclusion that uses only the p-values below 0.05 is ignoring the ranks where the threshold is loosest.
- The cumulative take-up curve per exposed user should be monotone increasing and flatten, while the weekly rate falls. Both are the same data, which is the point.
- New users during the test should show no decay if the decay is novelty. If they decay the same way, novelty is not the explanation.
- Re-run the segment slice on an A/A period: if it also produces two or three significant cells, the slicing procedure itself is the finding.
Follow-up
- The three significant cells are all in the same segment. Does that change your reading, and how would you test whether it is a real interaction rather than three correlated slices of one population?
- Design the long-term holdback: what size, for how long, and what does it cost you in foregone treatment?
- If cumulative take-up is the estimand, what is the right horizon and how do you keep it from being chosen after seeing the data?
Trailing thirty day volume per customer drops week over week
Hard · seasonality · day of week · window alignment
The trailing 30-day settled volume per active customer is down 7 percent against the same metric seven days earlier. Nothing shipped. You have fct_payment_authorization with requested_at, channel, captured_at, captured_amount_minor, settled_at, settlement_amount_minor, plus dim_customer with is_current, kyc_status, onboarded_at and closed_at for the active denominator. Before anyone writes a retention narrative, decide how much of the 7 percent is calendar structure, and hand back a calendar-robust version of the comparison.
Approach
- Do the window arithmetic first. Thirty days is four whole weeks plus two days, so exactly two weekdays appear five times and the other five appear four times. Sliding the window by seven days changes which two, and card-present and card-not-present volumes differ sharply by weekday.
- Rebuild on a 28-day window, which contains exactly four of every weekday, and see how much of the 7 percent survives. That single change removes the weekday composition effect with no modelling and no assumptions.
- Count the structural events inside each window: public holidays, and the billing anchor days that recurring authorizations cluster on. A window holding one fewer month boundary loses a block of recurring volume that has nothing to do with customer behaviour.
- Decompose by channel, since recurring, card_present and ecommerce have different calendar signatures. A drop concentrated in recurring points at anchor-day placement; one spread evenly across channels does not.
- Compare year over year at a 364-day lag rather than 365, which preserves weekday alignment, and only then read the residual.
- Check the denominator on its own. Active customer counts on a trailing window carry their own calendar structure, and a ratio can move because either side moved.
Follow-up
- Which window goes on the executive dashboard, and what do you give up by choosing it?
- How would you handle a holiday that moves between years, so that a 364-day lag still misaligns it?
- If a genuine 2 percent residual survives, what is the smallest cut that tells you whether it is breadth or depth?
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.
Rigour against a deadline
Nearly every data role forces a trade between the analysis you want and the one that fits the decision window. Prepare a case where you deliberately shipped something less rigorous, named the weakness to the person relying on it, and said what would change your answer. The naming is the part interviewers listen for.
Tell me about a time you had to influence a stakeholder who disagreed with your findings.
Medium · behavioural and stakeholder questions
Approach
- Name the disagreement or constraint, and how you resolved it with evidence.
- Close with what you would do differently, concretely.
- Quantify the outcome, including what you would not claim credit for.
Follow-up
- What would you do differently if you ran that project again?
- How did you know the outcome was caused by your change?
Defend a vintage finding that contradicts the portfolio dashboard
Medium · vintage analysis · mix shift · stakeholder pushback
The lending dashboard shows blended 90-plus days-past-due falling for four consecutive quarters while originations grew 60 percent. Using fct_loan_performance_monthly, you build a vintage view keyed on origination_month by months_on_book and find the three most recent vintages are worse than their predecessors at the same age. The business lead presents that dashboard weekly and pushes back hard, suggesting you picked favourable cohorts. You get one meeting and the vintage table. Present the finding so it survives the cherry-picking objection and ends in a decision.
Approach
- Reconcile before you contradict: show that aggregating your vintage table along the calendar diagonal reproduces the published blended series, so the disagreement is about age mix rather than about data quality.
- Make the mechanism arithmetic rather than rhetorical: a loan cannot reach 90 days past due before it is 90 days old, so rapid origination growth shifts weight onto young months-on-book where the rate is structurally near zero.
- Show every vintage rather than a selected pair, all indexed at months_on_book equal to 12, with cohort sizes printed beside each curve so nobody can claim the divergence rests on a thin cohort.
- Handle restructuring explicitly, because restructured_flag resets days_past_due: count each loan on its worst pre-restructure state, or recent vintages will look better than they are.
- Close on the decision rather than the chart: state what the divergence implies for the cutoff or the channel mix, and state in advance what evidence would make you withdraw the claim.
Follow-up
- Two cohorts differ at month 12. How do you separate a seasoning effect from a genuine credit-quality effect?
- Someone argues the recent vintages are simply a broker-channel mix shift. How do you test that, and what would confirm it?
Recommend a decision whose true outcome matures a year later
Hard · decision under censoring · leading indicators · staged rollout
An underwriting rule change must be decided in six weeks. Its real outcome, the vintage 90-plus rate at months_on_book 12 in fct_loan_performance_monthly, matures in a year. The executive wants a yes or no, not a range. Randomising the credit decision across the whole population is not available. Name the leading indicator you would accept, state its bias and the direction of that bias, define the decision rule and stopping condition before any rollout starts, and say what reading would make you recommend reversing the change.
Approach
- Fix the readout before the rollout, because a readout chosen after the data arrives is a story rather than a decision rule: indicator, window, threshold and reversal condition all go in writing first.
- Choose the leading indicator on its measured relationship to the matured outcome in historical vintages rather than on availability. Early delinquency, typically the share reaching dpd_1_29 or missing a first scheduled payment by months_on_book 3, is the usual candidate, and you quantify how well it predicted the 12-month rate across past cohorts.
- State the bias and its direction plainly: early delinquency under-represents default that emerges later and is contaminated by servicing and payment-date effects, so treat it as a floor on risk rather than an estimate of it.
- Buy identification where full randomisation is unavailable: a narrow randomised approval band around the cutoff, or a staged rollout by channel or region read as a difference-in-differences, with the parallel-trends assumption stated and checked in the pre-period rather than assumed.
- Give the executive the binary they asked for with the trigger attached in the same sentence: yes, conditional on the month-3 indicator staying inside a stated band, with an automatic hold if it breaches.
Follow-up
- How would you validate that the month-3 indicator predicts the 12-month outcome, and what evidence would invalidate it mid-rollout?
- Compliance refuses a randomised band. What is your next-best identification strategy, and what precision do you lose by taking it?