Interview guides · Desjardins
Desjardins Data Scientist Interview Questions & Guide 2026
Practice explaining analysis before a Desjardins data scientist interview: the recommendation, the uncertainty, the follow-ups.
The 60-second brief
As a Data Scientist at Desjardins, you are at the intersection of complex financial modeling and large-scale data architecture. Desjardins relies on its data science teams to maintain the integrity of its financial services, protect its members through sophisticated fraud detection, and optimize credit risk assessments. You will be responsible for translating raw, high-volume financial data into actionable intelligence that directly impacts the organization’s bottom line and the security of its members.
A large share of questions open as "how would you measure X", where the real work is choosing the metric, fixing its denominator, and defining the population it applies to. Any computation comes last and is frequently not required at all.
Focus areas
- Report only matured cohorts for loss metrics
- Separate authorization, settlement and dispute outcomes cleanly
- Read vintage curves, not blended portfolio averages
Overview
This role is critical to the stability of Desjardins. Whether you are working in Fraud Detection, Finance Functions, or Model Validation, your work provides the analytical backbone for strategic decision-making. You will collaborate closely with cross-functional teams, including engineering, risk management, and product stakeholders, to build models that are not only accurate but also compliant with rigorous industry regulations. It is a role for those who enjoy solving high-stakes problems where precision and ethical considerations are paramount.
The Desjardins interview process
Desjardins 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 Interviews
Before anything else, this round is a reading test. You are given a small schema and a question phrased in business language, and most of the difficulty sits in the gap between them. Who counts as an active user, does a refunded order still count as an order, is that date column an event time or a load time. Weak answers start typing immediately and compute something precise about the wrong population. Strong ones pin the definition in one sentence, name the column that encodes it, then write the query. On a timed assessment with nobody to tell, write the definition in a comment anyway.
What to demonstrate
- Whether an ambiguous term becomes a specific column and filter before any computation happens
- Whether you read the schema for keys and cardinality rather than only for column names
- Whether the result answers the question at the grain it was asked at, per user or per session or per day
How to prepare
- Take three metrics you already use and write down the exact filter and exact grain behind each, then practise stating one of them in a single sentence out loud
- On a schema you have never seen, spend the first minute writing what one row of each table means and which key it is unique on, then predict which joins can duplicate rows
- Rehearse a version where the definition changes halfway through, and edit the query you have instead of starting over
Panel Interview
A day of back-to-back interviews samples your floor, not your ceiling. Four hours in, the habits that carry a good answer are the first to go: restating the question before solving it, asking what the data would have to look like, checking a number before quoting it. What the day decides is whether the tired version of you is still someone to leave alone with an ambiguous problem. The round that sinks a candidate is usually not the hardest one. It is the one immediately after the round that went badly.
What to demonstrate
- Whether the late rounds get the same clarifying questions as the first one, or whether you start answering immediately to save effort
- Whether a weak answer stays in the room it happened in, instead of following you into the next conversation as apology or distraction
- Whether the quality of your questions holds up, since fatigue removes curiosity about the problem before it removes knowledge of the method
How to prepare
- Rehearse the length, not just the content: book four mock interviews of different types in one afternoon with short gaps, because the one you need to observe is the fourth
- Put the two or three questions you ask at the start of any problem on a card in front of you, so that under fatigue it is a habit you run rather than a decision you make
- Decide in advance what the gap between rooms is for: water, one line of notes on anything you promised to follow up, and an explicit close on the round that just ended so it does not travel
- Prepare a different closing question for each interviewer, so the end of a long day does not produce the same one four times
Where candidates lose points
Using written premium as the denominator of a loss ratio
Premium is written at inception and earned pro rata across the exposure period, so in a growing book written premium runs ahead of earned premium and the loss ratio comes out too low, with the error reversing when the book shrinks. The numerator has the mirror-image problem if it omits incurred-but-not-reported reserves, since recent accident periods then look profitable twice over. Both sides must refer to the same exposure period, which is what an accident-period view at a fixed development age enforces.
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.
Analysing at a different unit than the one randomised
Say out loud what was randomised (user, device, account, cluster) and make the analysis unit match, or account for the clustering with cluster-robust standard errors, the delta method, or aggregation up to the randomised unit. Randomising users and then running a test over sessions understates variance and inflates the false-positive rate.
Accepting a metric definition without asking about the denominator
Pin down the denominator, the eligibility filter and the time window before computing anything: conversion rate per session, per user, per eligible user and per new user are four different numbers with different behaviour. Restate the definition in one sentence and get agreement before you analyse.
Questions & practice
How do you balance the trade-off between false positives and false negatives in a credit risk model?
Medium · machine learning and modelling
Approach
- Pick an evaluation metric that matches the cost of each error type, not a default.
- Say how the offline result would be validated online before it is trusted.
- 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?
Measure calibration of a twelve-month default probability from scratch
Hard · calibration · brier score · binning
fct_loan_application gives application_id, model_pd_12m, model_version, decision, funded_at and loan_id. fct_loan_performance_monthly gives loan_id, months_on_book, days_past_due and charge_off_flag. Define the outcome as ever 90 or more days past due, or charged off, by months_on_book = 12. Without sklearn or scipy, build an equal-count binned reliability table, the expected calibration error, the Brier score and its reliability, resolution and uncertainty components, and report the residual the binned identity leaves behind. Restrict to cohorts that have actually reached 12 months on book.
Approach
- Build the label first and name the population it covers out loud: only funded loans have outcomes, so this measures calibration on the approved population. The declined region is unmeasured, and no binning scheme repairs that.
- Restrict to applications whose loans have reached months_on_book = 12. A cohort observed at 8 months has a mechanically lower default rate and will read as systematic over-prediction that is really just immaturity.
- Bin by equal count, deciles of model_pd_12m through a rank-based cut, not equal width. The PD distribution is heavily right-skewed, so equal-width bins put most of the mass in the first bin and leave the risky bins with single-digit counts whose observed rates mean nothing.
- Per bin compute n, mean predicted, observed rate, and the binomial standard error sqrt(o(1-o)/n) so a gap can be read against noise. ECE is the count-weighted mean absolute gap between mean predicted and observed.
- Compute Brier directly as the mean squared error, then reliability = sum of n_k (pbar_k - obar_k)^2 over N, resolution = sum of n_k (obar_k - obar)^2 over N, uncertainty = obar(1 - obar). Report residual = Brier - (reliability - resolution + uncertainty). That identity is exact only for discrete forecasts, so with binned continuous scores the residual is the within-bin spread of the score; a large one means the bins are too wide to support the decomposition.
- Split by model_version. A mixed-version population can look well calibrated in aggregate while each version is biased in opposite directions.
Follow-up
- AUC is unchanged after a population shift but the reliability curve has moved. What happened, and what do you do about it?
- How would you recalibrate without retraining, and what would you check afterwards?
- The top decile shows observed default well above predicted. Is that a calibration problem or a policy problem?
Implement accident-quarter loss ratio at twelve months development
Medium · loss ratio · development age · earned premium
fct_policy_period_monthly arrives as a stack of month-end snapshots: each row carries valuation_month alongside as_of_month, policy_id, product_line, written_premium_minor, earned_premium_minor, paid_loss_minor, case_reserve_minor, ibnr_reserve_minor and loss_adjustment_expense_minor. Compute the accident-quarter loss ratio at exactly 12 months of development: incurred losses over earned premium, both taken from rows whose as_of_month falls in the accident quarter, read from the snapshot 12 months after that quarter closes. Report quarters that cannot reach that age as incomplete rather than dropping them.
Approach
- Derive accident_quarter from as_of_month, then define the evaluation snapshot per quarter as valuation_month equal to the quarter's final month plus twelve months. Every figure in the ratio comes from that one snapshot, not from whichever snapshot happens to be newest.
- Numerator is paid_loss_minor plus case_reserve_minor plus ibnr_reserve_minor over the accident quarter's rows in that snapshot. Loss adjustment expense may be included or not, but the choice applies to every quarter and is named in an output column.
- Denominator is earned_premium_minor over the same rows. Written premium is booked in full at inception, so in a growing book it runs ahead of earned premium and drags the ratio down, with the error reversing when the book shrinks.
- Left-join the full quarter list against available valuation months so a quarter with no 12-month snapshot yields status incomplete and a null ratio, instead of disappearing and shortening the series without saying so.
- Split by product_line, since both the loss ratio level and the speed of development differ by line, and a blended series moves with mix as much as with experience.
Worked solution (30 min)
- Add accident_quarter and a target_valuation column equal to the quarter end plus twelve months.
- Filter rows to those where valuation_month equals the row's target_valuation, then assert each accident_quarter has exactly one distinct valuation_month left.
- Aggregate incurred and earned premium by accident_quarter and product_line and take the ratio.
- Reindex against the full list of accident quarters and product lines, marking rows with no matching snapshot as incomplete with a null ratio.
- Recompute one quarter by hand on a five-policy subset and confirm it matches.
Expected result: One row per accident_quarter and product_line with incurred, earned_premium, loss_ratio, an lae_included flag and status in complete or incomplete. Complete quarters end twelve months before the latest valuation_month, so the four or five most recent quarters carry null ratios.
Verify it yourself
- Assert exactly one distinct valuation_month per accident quarter after filtering; more than one means the snapshot join fanned out.
- For a growing book, earned premium in a quarter is below written premium booked in that quarter; if it is above, either the book is shrinking or the accident-quarter derivation is wrong.
- Incomplete quarters carry null ratios and never appear on the same line as complete ones in any chart.
- Swapping the denominator to written premium shifts a quarter by L(W - E)/(EW), so the sign of the change is the sign of written minus earned premium in that quarter: the ratio falls where the book grew and rises where it shrank. Verify the swap per quarter against sign(W - E) rather than expecting a uniform move; a series that moves in one direction everywhere only means the book grew in every quarter.
Follow-up
- The most recent complete quarter came in four points better than the one before. What do you check before calling it an improvement?
- How would you estimate the 12-month figure for a quarter that is only 6 months developed, and how would you label the estimate?
- Why can an expense ratio legitimately use a different denominator from the loss ratio in the same presentation?
How would you optimize a slow-running query that joins multiple large transaction tables?
Medium · sql and data manipulation
Approach
- 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.
- Say which table is the grain you start from, and join outward from it.
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 using window functions to identify users with consecutive days of suspicious transaction activity.
Medium · sql and data manipulation
Approach
- Check whether any join is one-to-many before aggregating, or the sums inflate.
- 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 would you verify this result without re-running the same query?
- What breaks if events arrive late or out of order?
Monthly delinquency roll rates with honest exiting-account denominators
Hard · roll rates · window functions · self joins
From fct_loan_performance_monthly, compute month-over-month roll rates: for each as_of_month_end and delinquency_bucket, the share of loans in that bucket that are in a worse state at the following month end. Use this disposition convention and apply it without exception: charge-off is the terminal state one rank past dpd_90_plus, so a loan that charges off has rolled forward, not exited; loans that leave the book any other way — prepaid in full, sold, matured, or simply absent at the next month end — are exited. Bucket order is current, dpd_1_29, dpd_30_59, dpd_60_89, dpd_90_plus, then charged_off. Return from_month, from_bucket, accounts, rolled_forward, cured, stayed, exited and roll_rate, and show that the four dispositions sum to accounts on every row.
Approach
- Map the five delinquency_bucket values to ranks 0 through 4 in a small inline VALUES list, and reserve rank 5 for charged_off, because 'worse' is an ordering statement and enum text does not sort that way reliably. Read charge-off from charge_off_flag on the next month's row rather than from delinquency_bucket, which keeps carrying whatever delinquency value the loan held when it was written off.
- Use LEAD(as_of_month_end), LEAD(bucket_rank) and LEAD(charge_off_flag) OVER (PARTITION BY loan_id ORDER BY as_of_month_end), then verify the leading month end is exactly one month after the current one, since a loan missing a month would otherwise appear to cure or roll across a gap it never traversed.
- Classify each loan-month into exactly one of four labels off the adjacent next row, whose rank is 5 when its charge_off_flag is true and its mapped bucket rank otherwise. rolled_forward: the next rank is strictly greater. cured: strictly lower. stayed: equal. exited: there is no adjacent next row at all, which is where prepayment, sale, maturity and a gap in the monthly history land.
- Define rolled_forward as 'any strictly worse rank', not 'rank plus one'. Under normal monthly aging the only reachable worse state is the next bucket, so the two definitions coincide — except at charge-off, where a loan can go from dpd_60_89 straight to rank 5. A strict plus-one test leaves those loan-months matching no branch and breaks the exhaustiveness invariant.
- Be explicit about why charge-off is a roll rather than an exit: routed to exited, dpd_90_plus has no worse state left, so its roll_rate is identically zero by construction — arithmetic, not a credit finding. At rank 5 the dpd_90_plus row reports roll-to-loss, which is the number a loss forecast actually consumes.
- Aggregate by from_month and from_bucket and require that rolled_forward + cured + stayed + exited equals accounts on every row, which is the invariant that proves the classification is exhaustive and mutually exclusive.
Worked solution (45 min)
- CTE ranks: an inline VALUES mapping of the five delinquency_bucket values to ranks 0 through 4, joined onto the monthly rows. Rank 5 is not in the map; it is assigned during classification.
- CTE leads: add LEAD(as_of_month_end), LEAD(bucket_rank) and LEAD(charge_off_flag) partitioned by loan_id ordered by as_of_month_end.
- CTE classified: derive next_rank as CASE WHEN next_charge_off_flag THEN 5 ELSE next_bucket_rank END, then a CASE expression producing exactly one label per loan-month — exited when the next month end is null or is not the current month end plus one month, otherwise rolled_forward / cured / stayed by comparing next_rank to bucket_rank.
- Aggregate to from_month and from_bucket with COUNT(*) and four FILTER counts, then compute roll_rate as rolled_forward over accounts with a numeric cast.
- Assert the exhaustiveness invariant in a final HAVING or a separate verification query before trusting any number.
Expected result: One row per month and originating bucket, with the four disposition counts summing to accounts on every row and roll_rate between 0 and 1. Roll rates climb across current, dpd_1_29, dpd_30_59 and dpd_60_89, because each successive bucket holds loans that have already failed to cure. Do not expect that climb to continue into dpd_90_plus: that bucket is a catch-all holding several monthly cohorts at once until the charge-off policy retires them, so its roll-to-loss rate is set by the policy horizon — under a 180-day policy roughly a third of the bucket rolls in a given month — and usually sits below the dpd_60_89 rate. Cure out of dpd_90_plus is well under ten percent.
Verify it yourself
- rolled_forward + cured + stayed + exited equals accounts for every row, with no exceptions.
- SUM(accounts) for a given from_month equals the count of loan rows at that month end, so no loan-month was dropped or counted twice.
- Removing the adjacency test changes the cure and roll counts, and the size of that change is exactly the number of loans with a gap in their monthly history.
- The rolled_forward count for from_bucket dpd_90_plus in month M is at least the number of loans whose first charge_off_flag = true row falls at month end M+1. If it is materially lower, charge-off is being routed into exited.
- roll_rate for dpd_90_plus is strictly greater than zero in any month with charge-offs. A flat zero on that bucket across every month is the signature of charge-off classified as an exit, not of an unusually stable book.
- No loan-month is counted as both rolled_forward and exited: a loan with a charged-off row at M+1 has an adjacent next row, so it can never reach the exited branch.
Follow-up
- Project the next three months of dpd_90_plus inflow from these roll rates. What assumption does that projection make, and when does it break?
- A restructure resets days_past_due to zero. What does that do to the cure rate out of dpd_60_89, and how would you separate a real cure from a reset?
- Argue the other convention: prepayment in exited but charge-off also in exited, with roll rates reported only for the four non-terminal buckets. What does that series answer better, and what does it lose?
- The charge-off policy changed from 180 to 120 days. Show where that appears in this table and what it does to the dpd_90_plus roll rate specifically.
Describe a product you admire; what metrics would you use to measure its success?
Medium · product sense and metrics
Approach
- Name one primary metric, then the guardrail that stops it being gamed.
- Decompose the metric into the rates that drive it, and say which one you would check first.
- Restate the decision this analysis has to support, and who acts on the answer.
Follow-up
- What would you do if the primary metric and the guardrail moved in opposite directions?
- Which segment would you cut first, and what would that rule out?
How would you define "success" for a new personalized banking feature?
Medium · product sense and metrics
Approach
- State what result would change your recommendation, so the answer is falsifiable.
- Restate the decision this analysis has to support, and who acts on the answer.
- Name one primary metric, then the guardrail that stops it being gamed.
Follow-up
- Which segment would you cut first, and what would that rule out?
- How would you detect that the metric is being gamed rather than genuinely improving?
How do you prioritize tasks when you are working on multiple high-impact projects simultaneously?
Medium · product sense and metrics
Approach
- Decompose the metric into the rates that drive it, and say which one you would check first.
- Name one primary metric, then the guardrail that stops it being gamed.
- Restate the decision this analysis has to support, and who acts on the answer.
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?
If we notice a sudden 10% drop in loan application approvals, how would you go about diagnosing the root cause?
Medium · product sense and metrics
Approach
- Decompose the metric into the rates that drive it, and say which one you would check first.
- State what result would change your recommendation, so the answer is falsifiable.
- Restate the decision this analysis has to support, and who acts on the answer.
Follow-up
- What would you do if the primary metric and the guardrail moved in opposite directions?
- Which segment would you cut first, and what would that rule out?
What are the most common pitfalls when designing an A/B test in a high-variance environment?
Medium · a/b testing and experimentation
Approach
- Name the guardrails that would stop a launch even on a positive primary result.
- State the primary metric and the minimum effect worth shipping, then size the test.
- Decide the analysis before seeing data, including how long it runs and when you look.
Follow-up
- How would you handle interference between treated and control units?
- What would you conclude if the result is positive but the test is underpowered?
How do you determine the required sample size for an experiment before it launches?
Medium · a/b testing and experimentation
Approach
- Decide the analysis before seeing data, including how long it runs and when you look.
- State the primary metric and the minimum effect worth shipping, then size the test.
- Name the guardrails that would stop a launch even on a positive primary result.
Follow-up
- What would you conclude if the result is positive but the test is underpowered?
- What would you do if you could not randomise at all?
Cut variance on a heavy-tailed settled-volume metric
Medium · cuped · stratification · heavy tails
The primary metric is settled payment volume per customer per month in one reporting currency, randomised by customer_id, computed from fct_payment_authorization rows that are approved, captured and not reversed. The available population powers a minimum detectable effect of 3.0 percent, but the plausible effect is about 1.2 percent. You have eight pre-randomisation weeks of the same metric per customer, and the distribution is heavily right-skewed. Propose a variance-reduction plan, state the estimator and its preconditions, quantify the improvement you expect, and say whether it closes the gap.
Approach
- Apply CUPED with the pre-period metric as the covariate. Define Y_adj = Y - theta * (X - Xbar) with theta = Cov(Y, X) / Var(X) estimated on the pooled experiment data. The variance of the adjusted difference is Var(Y) * (1 - rho^2) where rho is the correlation between Y and X, so the entire benefit is determined by rho and nothing else.
- Check the precondition that makes the estimator unbiased: X must be measured strictly before assignment. A covariate computed over any window that overlaps the test period absorbs part of the treatment effect and biases the estimate toward zero. Pin the covariate window end date to the randomisation timestamp, not to the analysis date.
- Handle customers with no pre-period. Set X to zero plus a no-history indicator, or stratify them as their own cell. Either is valid because the imputation rule is fixed before assignment and does not depend on the arm; what is invalid is imputing with an arm-specific mean.
- Attack the skew separately, because CUPED reduces variance but does not make a heavy tail light. Pre-register a winsorisation point such as the 99.5th percentile of the pre-period distribution, state plainly that this changes the estimand from the mean to a capped mean, and report both. Capping at a pre-period quantile rather than a post-period one keeps the cap from depending on the treatment.
- Stratify the assignment on pre-period decile crossed with segment, which removes the same variance CUPED does when the covariate is the stratifying variable and additionally guarantees balance rather than achieving it in expectation. Post-stratified analysis is the fallback if assignment is already running.
- Pin the currency conversion to a fixed rate table dated at test start. Using settlement_fx_rate as realised injects market movement into the metric, and if the arms have different currency mixes it injects it unequally.
Worked solution (25 min)
- Estimate rho on historical data by correlating each customer's eight-week pre-period volume with their following-month volume; assume the answer is 0.62.
- Compute the variance multiplier: 1 - rho^2 = 1 - 0.3844 = 0.6156, so the standard error falls by sqrt(0.6156) = 0.785 and the effective sample size rises by 1 / 0.6156 = 1.62.
- Apply to the MDE: 3.0 percent * 0.785 = 2.36 percent.
- Invert the requirement: reaching 1.2 percent needs a variance multiplier of (1.2 / 3.0)^2 = 0.16, which requires rho = sqrt(1 - 0.16) = 0.92. That correlation is not attainable from an eight-week pre-period on this metric.
- Price the remaining shortfall in duration. Variance of the mean falls as 1/n, so the extra exposure needed is the ratio of the variance multipliers taken the right way round: 0.6156 / 0.16 = 3.85. The CUPED-adjusted plan must run about 3.85 times its planned exposure to detect 1.2 percent, holding rho and the traffic mix fixed; put the other way, the plan as written carries only about 26 percent of the sample it needs.
Expected result: At rho = 0.62, CUPED lowers the MDE from 3.0 percent to about 2.36 percent, a 21 percent reduction, and raises effective sample size by 1.62x. It does not reach 1.2 percent; that would need rho of about 0.92. Closing the rest with duration alone takes about 3.85 times the CUPED-adjusted exposure (0.6156 / 0.16), so the plan as written holds roughly 26 percent of the sample required. The correct deliverable states the shortfall and prices that additional duration rather than declaring the test adequately powered.
Verify it yourself
- The duration factor must come out the same by both routes: 0.6156 / 0.16 = 3.85, and squaring the MDE ratio 2.354 / 1.2 = 1.962 also gives 3.85. A factor below 1 means the ratio has been inverted, since more sample is needed, not less.
- Run CUPED on an A/A split of historical data: the point estimate must stay centred on zero and the standard error must fall by the predicted factor. If the estimate moves, the covariate is contaminated by post-randomisation data.
- Regressing Y on X with an arm indicator should give an arm coefficient numerically equal to the CUPED-adjusted difference; the two are the same estimator.
- Recompute rho within each segment. If the pooled rho is much higher than every within-segment rho, the gain is coming from between-segment variation that stratification already removes and should not be double counted.
Follow-up
- Your pre-period correlation is 0.62 on existing customers and near zero on customers onboarded in the last month. How do you report a single effect across both groups without the low-correlation group eating the gains?
- Someone proposes using a post-randomisation covariate that correlates 0.9 with the outcome. Why is that not a better CUPED covariate?
- If you winsorise, what do you say to a sponsor who points out that the capped customers are the ones the business cares about most?
Settled volume jumps after a new acceptance corridor launches
Medium · minor units · currency conversion · reconciliation
Settled payment volume per active customer rose 12 percent in the month a new acceptance corridor went live. The pipeline sums settlement_amount_minor from fct_payment_authorization and divides by 100 to get major units. Columns available: amount_minor, transaction_currency, captured_amount_minor, settlement_amount_minor, settlement_currency, settlement_fx_rate, settled_at, is_reversal, issuer_country, acquirer_country. Confirm or refute the 12 percent, and specify exactly the conversion the metric should be using.
Approach
- Split the month-over-month increase by settlement_currency. If one currency carries nearly all of it while its transaction count barely moved, the problem is arithmetic rather than demand, and that is a two-minute check.
- Look up each currency's ISO 4217 exponent. Dividing a three-decimal currency by 100 instead of 1000 overstates it tenfold; dividing a zero-decimal currency by 100 understates it hundredfold. Neither is a rounding issue.
- Rewrite the conversion to scale settlement_amount_minor by ten to the power of that currency's exponent, then convert to the reporting currency. Do not reuse settlement_fx_rate for this; it is the rate applied between transaction and settlement currency at settlement time, not a reporting-currency rate.
- Check the other half of the series too. settlement_amount_minor is denominated in settlement_currency while amount_minor is in transaction_currency, so any series that mixes the two is uninterpretable regardless of the exponent fix.
- Exclude reversals and refunds, recompute, then reconcile the corrected total to the settlement ledger for the same period and show the tie-out in the write-up.
- Recompute the per-customer denominator on the corrected month, since a new corridor adds customers as well as volume and the ratio can move either way once the numerator is right.
Follow-up
- Where else in the warehouse does a hardcoded divide-by-100 appear, and how would you find every instance?
- What test would fail the build the next time a currency with a different exponent is onboarded?
- The reporting-currency rate is itself a choice. Which rate, on which date, and why does finance care about the answer?
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.
The project you argued against
Saying no well is a senior skill and it is rarely rehearsed. Think of a time you told someone their analysis was not worth doing, or that the experiment could not answer their question at the sample size available. Explain what you offered instead. Refusal without an alternative reads as obstruction rather than judgement.
How do you handle cases where your A/B test results are statistically significant but practically meaningless?
Medium · behavioural and stakeholder questions
Approach
- Close with what you would do differently, concretely.
- Quantify the outcome, including what you would not claim credit for.
- Pick a story where you drove the decision, not one where you observed it.
Follow-up
- What did you decide not to do, and why?
- How did you know the outcome was caused by your change?
Turn a one-line fraud-number request into a scoped brief
Easy · scoping · metric definition · denominators
A stakeholder messages: what is our fraud rate, and is it going up? You have fct_payment_authorization, fct_card_dispute and dim_customer. At least four defensible answers exist: count-weighted or value-weighted, attributed to the transaction month or to the dispute filing month, and gross or net of recoveries and successful representments. You get one reply before someone else produces an uncaveated number. Write that reply: the clarifying questions you ask, the single default you will produce if nobody answers, and what the default excludes.
Approach
- Establish the decision behind the question first, because a risk-rule change, a board number and a merchant contract negotiation need different denominators, and asking which one is not stalling.
- Offer a short menu rather than an open question: a stakeholder can choose between two named options but cannot specify a denominator from scratch.
- Commit to a default so the reply is useful even if nobody answers, for example net fraud loss in basis points of settled volume, attributed to the requested_at month, matured months only.
- State the exclusions in the same breath as the default: non-fraud dispute categories, transaction months with less than 120 days of maturity, and first-party abuse that arrives coded as consumer_dispute.
- Give a delivery time for the default and a longer one for the fuller cut, so the choice between them carries a visible cost.
Follow-up
- They come back wanting it by merchant for a contract negotiation. What changes in the definition and in the maturity rule?
- How would you separate first-party abuse from third-party fraud in this data, and what would you refuse to conclude from the split?
Explain an incomplete dispute chart to a non-technical executive
Easy · dispute maturity · stakeholder communication · right-censoring
A finance lead is looking at first-chargeback rate by transaction month, built from fct_card_dispute joined to fct_payment_authorization on auth_id and attributed to requested_at. The last three months slope sharply down and the lead wants to announce a fraud improvement at tomorrow's review. Consumer dispute rights commonly run around 120 days from the transaction or expected delivery date, so those months are not complete. In five minutes, with no statistics vocabulary, explain why the decline is not yet evidence and say exactly what you would put on the slide instead.
Approach
- Lead with the mechanism in the listener's own terms, not with the statistical name for it: a dispute is attributed to the month the transaction happened, but it can be filed up to roughly 120 days later, so recent months contain only the disputes filed so far.
- Show completeness rather than arguing about the rate: for each transaction month, plot the share of its eventual disputes already filed, estimated from months that are fully matured. The last three months will sit visibly below 100 percent.
- Replace the chart with two artefacts: a matured series that stops 120 days back and is labelled final, and a development-factor estimate for the immature months drawn as a dashed range and labelled an estimate.
- Hand over one sentence the executive can repeat without you in the room: the recent months look better because the disputes have not arrived yet, not because fewer will arrive.
- Offer a weekly signal they can watch instead, such as the risk-score mix of approved volume or the decline-rule hit rate, and state up front what it does and does not predict.
Follow-up
- The deck ships tomorrow regardless. What exactly goes on the slide, and what wording do you insist on?
- How would you estimate the development factors, and how would you notice if they had shifted?