cswork / libraryOpen practice workspace

Interview guides · Impossible Foods

Impossible Foods Data Scientist Interview Questions & Guide 2026

Impossible Foods data scientist prep: probability, sampling distributions, and stating the assumption behind every test you run.

The 60-second brief

As a Data Scientist at Impossible Foods, you play a crucial role in driving insights that influence product development, enhance user experiences, and optimize business operations. Your analytical skills will be pivotal in interpreting complex datasets, enabling the company to innovate in the plant-based food industry. This role not only impacts product quality and sustainability but also supports strategic decision-making that can help Impossible Foods expand its footprint in the competitive food market.

SQL is seldom the hardest round and is often the one that eliminates people. The working bar is usually window functions, correct deduplication, and joins that do not silently fan out rows, rather than obscure syntax.

Focus areas

  • Join order, return and inventory grains without fan-out
  • Compute margin after discounts, returns and shipping
  • Separate true demand from stockout-censored sales

Overview

You will engage deeply with a variety of data sources, leveraging statistical methodologies and machine learning techniques to solve real-world challenges. From improving the taste and texture of products to optimizing supply chain processes, your contributions will directly influence the company's mission to create a sustainable food system. Expect to work collaboratively with cross-functional teams, including product development, marketing, and operations, which makes this role both dynamic and essential to the company's ongoing success.

The Impossible Foods interview process

Impossible Foods candidates report 5 rounds · ≈ 4-6 weeks. The stages below are what candidates describe, not a published process.

Technical Screening

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

Interviews with Managers

Rounds outside the standard loop often open with something deliberately under-specified: a loose business problem, an open question about a product area, a dataset described in one sentence. The common failure is surveying, listing six plausible approaches and committing to none of them. The thing that separates a strong answer is scoping out loud. State what you are treating as the goal, name the metric you would move, say what you are choosing not to do and why, then take one path through to an actual answer. An interviewer can follow you down a narrow path. Nobody can grade a menu.

What to demonstrate

  • Whether you turn an ambiguous prompt into a stated question with a measurable outcome before doing any work
  • The judgement visible in what you cut, and whether you say why you cut it rather than silently dropping it
  • Whether you land on a concrete recommendation with its caveat attached, rather than an unranked set of options

How to prepare

  • Take three vague prompts, such as 'is this feature working', 'why did retention drop', and 'should we expand into a new segment'. For each, write one sentence of goal, one primary metric with its window, and two things you are explicitly not doing.
  • Practise giving the recommendation first and the reasoning second, in five minutes. Loosely defined rounds are usually time-boxed, and an answer that arrives last often does not arrive.
  • Keep a running assumption list as you talk, on paper or in the shared doc, so the interviewer can challenge one assumption instead of your whole answer.

Cross-Functional Interviews

Because the format is not fixed, prepare the reasoning rather than the ritual. Nearly every version of this round draws on the same underlying material: a design you can defend, a metric you can define exactly, an analysis whose assumptions you can state out loud. Only the wrapper changes, whether that is a take-home, a live case, a deep dive on past work, or a rough estimate on a whiteboard. Answers rehearsed to fit one shape stall the moment the shape differs. Practise naming the assumption behind a number, then saying how much the conclusion moves if that assumption is wrong.

What to demonstrate

  • Whether your justification for a method survives the question 'why not the simpler thing', including when the simpler thing would have worked
  • Precision under pressure: what exactly counts as an active user, a conversion or a success, over what window, with what exclusions
  • Whether you carry an argument through to a recommendation instead of stopping at a list of tradeoffs

How to prepare

  • For each project you plan to mention, write the metric definition in one sentence: numerator, denominator, time window, exclusions. Say it out loud once, because vagueness shows up in speech before it shows up on paper.
  • Rehearse the same project at three lengths: two minutes, ten minutes, and a deep dive on one technical decision. Cutting live is harder than it sounds.
  • For your headline result, write down what would have had to be true for it to be wrong, and how you ruled that out.

Technical Assessments

This round decides whether someone can hand you a schema and a question and trust the number that comes back. Correctness under a clock is the bar, not clever syntax. The habit that separates strong from weak answers is checking the grain: after every join, know how many rows you expect and whether the count moved. Most wrong answers in this format are not wrong logic, they are a fan-out from a key that turned out not to be unique, or a filter applied before an aggregate when it belonged after. Say what you expect before you run it.

What to demonstrate

  • Whether your row counts survive each join, and whether you notice on your own when they do not
  • Deliberate handling of rows that fail to match, including whether the question needs an inner join or a left join with the non-matches kept and counted
  • Whether NULLs are treated on purpose, given that a NULL compares equal to nothing and that COUNT of a column skips it
  • Reaching a defensible answer inside the window instead of a refined one after it

How to prepare

  • Take a two-table schema, write a join that fans out on purpose, then fix it by collapsing the many-side to one row per key before joining. Repeat until the fix is reflex rather than recall.
  • Write a funnel as one query and print the distinct user count at each stage, then confirm each stage is a subset of the one above it rather than assuming it
  • Do a few timed runs in a plain text box with no autocomplete and no formatter, since assessment editors often have neither

Behavioral Interviews

Behavioural answers from data candidates get audited in a way that answers from other roles do not. When you say a model lifted retention, the next question is the denominator, the window, and how you knew the lift was not seasonal. So attach the measurement to each claim while you tell it: what the metric was before, over what period, and against what comparison. Numbers with no baseline read as rounded-up memory, and one unsupported figure tends to make the rest of the story sound rehearsed.

What to demonstrate

  • Whether each impact number arrives with a baseline, a window and a comparison, or as a bare percentage
  • Whether you can name the method that attributed the effect to your work (an experiment, a staged rollout, a seasonal control) or concede the link was correlational
  • Whether the magnitudes stay internally consistent when the interviewer multiplies them against the scale you described earlier

How to prepare

  • For each story, write the impact line as metric, value before, value after, window, and how attribution was established. Any line missing two of those five is a follow-up you will answer badly.
  • Re-derive one headline number from the source table rather than the deck that reported it. Resume numbers drift upward across retellings.
  • Decide in advance which figures you cannot share, and prepare the ratio or relative change you can give instead, so a confidentiality limit does not read as evasion.

Where candidates lose points

Fitting demand models on sales when sales are censored by availability

Units sold equal the minimum of demand and what was sellable, so every day a SKU was out of stock contributes a zero that looks identical to genuine indifference. A model trained on that history forecasts the stockout, the buy shrinks, availability falls further, and the error compounds each cycle, which is why a declining SKU forecast should always be checked against minutes_unavailable before it is believed. The fixes are to restrict the fit to in-stock periods, to model availability explicitly as an exposure term, or to use a censored likelihood; all three require the availability history to be retained at a finer grain than a daily end-of-day snapshot, which is exactly what teams tend to discard.

Crediting promotions and channels by last click, especially coupon codes

A discount code handed to a partner or an influencer is claimed by customers who were already at checkout, so last-click attribution books organic demand as partner-driven, and the reported return on that spend is an artefact of the code's visibility at the basket rather than of any persuasion. The same mechanism makes email and paid search look strong because they sit close to a purchase that other activity created. Incremental effects need a holdout, a geo split or a switched-off period; where none is possible, at minimum report the share of code redemptions from customers whose session began before any partner touchpoint, because that number alone usually collapses the claim.

Ending an analysis without a recommendation or next step

Close with what you would do and what would change your mind, stated as a condition you can check later. If the evidence is genuinely inconclusive, recommend the specific next measurement and say what it costs in time or exposure.

Extrapolating a first-week lift inflated by novelty effects

Plot the treatment effect by days since first exposure instead of quoting one pooled average. A lift that decays toward zero across the test window is behaviour that will not persist, and annualising it produces a forecast that misses by an order of magnitude.

Questions & practice

Permutation test for a geo holdout with pre-period adjustment

Hard · randomisation inference · difference-in-differences · geo experiment · blocking

geo_weekly has geo_id, week_start, sessions and net_revenue_cents for 40 geos over 8 pre-period weeks and 4 promotion weeks. Twenty geos were randomly assigned to run the promotion, twenty were held out, with assignment blocked into 20 pairs matched on pre-period sessions. The per-geo metric is net revenue per 1000 sessions. Estimate the difference-in-differences effect and test it with a permutation test you write from scratch, respecting the blocking. Report the estimate, a two-sided p-value, and a placebo test on the pre-period.

Approach

  1. Collapse to one pre value and one post value per geo first: revenue per 1000 sessions computed as sum(revenue) / sum(sessions) * 1000 within each period, never as the mean of weekly rates, which would weight a quiet week like a peak one.
  2. Compute delta_i = post_i - pre_i per geo, then the statistic as mean(delta) over treated minus mean(delta) over held-out. Pre-differencing absorbs the fixed level differences between geos, which is where most of the variance lives.
  3. Permute the way the assignment was actually made. Blocking into matched pairs means you flip the label within each of the 20 pairs independently, giving 2^20 possible relabelings, not C(40,20). Permuting freely across pairs tests a null the design does not correspond to and gives a p-value that is not calibrated.
  4. Draw 10000 relabelings with a seeded generator, recompute the identical statistic each time, and take p = (1 + count(|stat_perm| >= |stat_obs|)) / (1 + B). The plus-one is not cosmetic: it keeps the test valid at small B and makes p = 0 impossible.
  5. Run the placebo by splitting the 8 pre-period weeks into weeks 1 to 4 and 5 to 8, computing the same statistic and p-value with the real labels. A small placebo p-value means the matched pairs were not parallel before treatment and the headline estimate is not trustworthy.

Worked solution (40 min)

  1. Aggregate to geo-period, form the per-1000-session rate, and compute delta_i. Assert 40 rows and 20 intact pairs.
  2. Write stat(labels) as a single function of a boolean treated-vector so the observed statistic and every replicate go through the same code path.
  3. Generate 10000 sign vectors with rng.integers(0, 2, size=(10000, 20)), expand each to a 40-length label vector through the pair index, and evaluate stat on each.
  4. Compute p = (1 + (np.abs(perm) >= np.abs(obs)).sum()) / (1 + 10000) and report it with its Monte Carlo error, roughly sqrt(p*(1-p)/10000).
  5. Repeat the whole procedure on the pre-period split and report that p-value next to the headline one.

Expected result: A DiD estimate in cents per 1000 sessions, a two-sided p-value strictly between 1/(B+1) and 1, and a permutation distribution whose mean is within Monte Carlo noise of zero because each geo's delta enters symmetrically under the null relabeling.

Verify it yourself

  • Mean of the permutation distribution is approximately zero
  • The observed statistic recomputed by stat(observed_labels) matches the directly computed DiD exactly
  • The placebo pre-period p-value is not small; if it is, stop and report the pre-trend rather than the effect
  • Re-running with a different seed moves p by roughly its Monte Carlo error and no more

Follow-up

  • The promotion ran during a week with a national holiday in some geos. How does that show up here, and what would you add to the design or the analysis?
  • With 40 geos, what effect size does this design actually detect? Sketch how you would answer that before running the test.
  • Revenue is still provisional because returns are open. What does that do to a four-week post window, and how long would you hold the readout?

Correct a demand estimate for stockout-censored sales history

Hard · censoring · poisson exposure · lost sales · estimation

daily has 180 rows for one SKU at one node: snapshot_date, gross_units_sold, minutes_unavailable (0 to 1440), was_listed (bool), unit_paid_price_cents and is_promo. Recorded sales are the minimum of demand and what was sellable. Estimate the uncensored daily demand rate treating the available fraction of the day as exposure, then estimate the units lost to unavailability over the 180 days. Write the estimator yourself rather than calling a GLM library, and state the assumption the correction rests on.

Approach

  1. Define exposure_d = was_listed * (1440 - minutes_unavailable) / 1440, in day-equivalents. A day with zero exposure had nothing sellable, so its gross_units_sold is 0, and it contributes 0 to both the numerator and the denominator of the closed-form rate below: it cannot move the estimate in either direction. Drop those days from the fit anyway, because the equivalent log-exposure offset form is undefined at log(0) and any GLM you later swap in will fail on them. Keep every one of them in the lost-units sum, where they carry the largest term.
  2. Model sales as Poisson with mean lambda * exposure_d. For an intercept-only model with a log-exposure offset the maximum likelihood estimate has a closed form, lambda_hat = sum(sales) / sum(exposure), so no optimiser is needed; write that and derive it from the score equation rather than asserting it.
  3. Estimate lost units as sum over all 180 days of lambda_hat * (1 - exposure_d), which is the demand the model says arrived while nothing was sellable. Convert to lost revenue only at the unit price actually in force on those days, and mark it as an upper bound.
  4. State the assumption plainly: demand arrives at a constant rate within the day. It is wrong in the direction that matters, because a SKU that sells out does so after the day's demand has already concentrated in the earlier hours, so scaling by the available fraction understates true demand. End-of-day snapshots cannot distinguish a sellout at 09:00 from one at 23:00.
  5. Extend to lambda_d = exp(b0 + b1is_promo + b2log(price)) * exposure_d if promo days are a material share, fitting by Newton or by iteratively reweighted least squares, and check the sign of the price coefficient for plausibility before trusting the lost-units figure.

Follow-up

  • Some of the lost demand moved to a substitute SKU or a different node. How would you bound the share that was genuinely lost rather than displaced?
  • You only have end-of-day snapshots. What would you instrument to make this estimate honest, and at what data volume?
  • The forecast that drove the buy was fitted on this same censored history. Walk through what happens over three consecutive seasons if nobody corrects it.

Implement demand-weighted in-stock rate from a written definition

Easy · metric definition · weighting · pandas · aggregation

inventory has one row per snapshot_date, sku_id, node_id with columns node_type, minutes_unavailable (0 to 1440), was_listed (bool), forecast_units (nullable float) and gross_units_sold. Implement the weekly demand-weighted in-stock rate per node: numerator sums forecast_units * (1440 - minutes_unavailable) / 1440 over rows with was_listed True, denominator sums forecast_units over the same rows. Weeks start Monday. Also return the unweighted rate, the share of eligible rows dropped for a null forecast, and the count of SKUs behind each week.

Approach

  1. Write the eligibility filter once as a boolean mask (was_listed and forecast_units notna) and apply it to numerator and denominator together, since a row eligible for one and not the other silently changes the metric's meaning.
  2. Clip minutes_unavailable to [0, 1440] and check how many rows needed clipping, because an out-of-range value from a clock or restart bug produces an availability factor above 1 or below 0 and a rate that leaves [0, 1].
  3. Derive the week key with snapshot_date - to_timedelta(dayofweek, 'D') rather than dt.isocalendar().week, which returns a year-agnostic integer and collides across years at the turn of January.
  4. Aggregate numerator and denominator separately by (node_id, week) and divide at the end. Never take the mean of daily rates: a week rolls up by re-summing both sides, and a low-demand day would otherwise carry the same weight as a peak day.
  5. Compute the unweighted rate as the simple mean of the availability factor over the same rows and report both, so the long-tail distortion is visible rather than assumed.

Follow-up

  • forecast_units is the number the buy was placed against, so it is wrong exactly when the forecast was wrong. What else could weight this, and what does each alternative bias?
  • A SKU with was_listed False all week disappears from both sides. Is that the right behaviour, and how would you separate 'not stocked here' from 'stocked out here'?
  • The rate is 97 percent but the merchandising team says the top sellers are empty. What cut of this data would you show to settle it?

Ninety-day repeat rate by first-delivery cohort using window functions

Medium · window functions · cohort retention · self-join

Using fct_order_line (order_line_id, order_id, customer_id, quantity, delivered_at_utc, line_status) and fct_return_line (order_line_id, quantity_returned), define a kept line as a delivered line whose total returned quantity is less than its ordered quantity, and an order's delivery time as MAX(delivered_at_utc) across its delivered lines. Cohort each customer on the calendar month of their first order containing a kept line. Return, per cohort month, the cohort size and the share of customers who placed a later order with a kept line within 90 days of that first delivery. Report only cohorts matured 120 days.

Approach

  1. Pre-aggregate returns to order_line_id first, then mark kept lines; doing this before any customer-level logic keeps the fan-out out of the cohort counts.
  2. Roll kept lines up to an order grain CTE: order_id, customer_id, delivered_at = MAX(delivered_at_utc). State the choice, because MIN would date the order at its first parcel and shift borderline customers a cohort earlier.
  3. Pick the first order per customer with ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY delivered_at, order_id) = 1, breaking the tie on order_id so the result is deterministic across runs.
  4. Find repeaters with EXISTS against the same order CTE (later order_id, delivered or placed within the 90-day interval), not a join, so a customer with three qualifying second orders is still one numerator unit.
  5. Group by date_trunc('month', first_delivered_at) and filter to cohorts whose month ended at least 120 days ago, so every cohort has had the full window plus return maturity.
  6. Report cohort size next to the rate, since the ratio moves whenever acquisition volume moves.

Follow-up

  • Why cohort on first delivery rather than account_created_at_utc or first_order_placed_at_utc, and what does the choice do to accounts that order and cancel?
  • The 90 days runs from first delivery. What changes if you measure the second order by placed_at_utc rather than by its own delivery?
  • Repeat rate rose four points last quarter while revenue fell. What do you check first?

Find consecutive stockout runs per SKU and node with gaps-and-islands

Medium · gaps and islands · window functions · inventory

From fct_inventory_snapshot (snapshot_date, sku_id, node_id, available_to_promise_units, was_listed, minutes_unavailable, forecast_units), find maximal runs of consecutive calendar days over the last 90 days where was_listed = TRUE and available_to_promise_units = 0, for each SKU and node. Return sku_id, node_id, run_start, run_end, run_length_days and forecast_units_lost, the sum of forecast_units over the run, keeping only runs of three days or more. The table is not guaranteed to contain a row for every SKU-node-day, nor at most one; state how you treat a missing day and how you treat a duplicated one.

Approach

  1. Measure density and uniqueness before writing any window function. Per (sku_id, node_id), compare COUNT(), COUNT(DISTINCT snapshot_date) and the 90 days in the window. Rows fewer than distinct days means missing snapshots; COUNT() above COUNT(DISTINCT snapshot_date) means a SKU-node-day was written twice. They are different defects and they break different things.
  2. Filter to the stockout condition, then flag a new island with LAG: new_run = (snapshot_date - LAG(snapshot_date) OVER (PARTITION BY sku_id, node_id ORDER BY snapshot_date)) > 1 OR LAG IS NULL.
  3. Turn the flag into a group key with SUM(new_run::int) OVER (PARTITION BY sku_id, node_id ORDER BY snapshot_date ROWS UNBOUNDED PRECEDING).
  4. Know what the arithmetic key does before choosing between them. Over the same filtered rows, snapshot_date - ROW_NUMBER() * INTERVAL '1 day' yields identical islands to the LAG running sum whenever snapshot_date is distinct within the partition: any break in the date sequence, whether the day is missing or merely in stock, shifts the arithmetic key and trips the LAG flag alike. The two diverge only on duplicated dates, where ROW_NUMBER advances while the date stands still: the key steps back a day, splitting one run into two that overlap on the duplicated date, whereas the LAG form sees a zero-day gap, keeps one island, and inflates COUNT(*) and forecast_units_lost instead. DENSE_RANK() OVER (PARTITION BY sku_id, node_id ORDER BY snapshot_date) makes the arithmetic key duplicate-safe; collapsing to one row per SKU-node-day first is cleaner still.
  5. Decide and state the missing-day rule, because no island key decides it for you. Both forms end a run at a day with no row, which is the same as asserting the SKU was available that day. If a missing row instead means the snapshot job did not run, that day is unknown, and ending the run there shortens every incident that spans the outage.
  6. Make censoring visible rather than assuming it away. Left join a generated date spine and record, per run, whether the day before run_start and the day after run_end carry a row at all. A run bounded by an observed available day genuinely ended; a run bounded by an absent day may be a fragment of a longer one, and the two must not be reported as the same thing.

Worked solution (30 min)

  1. Run the density and uniqueness checks; record how many (sku_id, node_id) pairs have fewer rows than days in the window, and how many carry any duplicated snapshot_date.
  2. Build the filtered CTE, add the LAG-based new_run flag and the running-sum island key.
  3. Aggregate to one row per island: MIN and MAX of snapshot_date, COUNT(DISTINCT snapshot_date), COUNT(*) and SUM(forecast_units).
  4. Add run_length_days = run_end - run_start + 1, confirm it equals the distinct-day count on every row, and treat any row where COUNT(*) exceeds it as a duplicated SKU-node-day rather than a longer run.
  5. Left join the date spine at run_start - 1 and run_end + 1 and carry a censored flag onto the output.
  6. Filter to run_length_days >= 3 and order by forecast_units_lost descending.

Expected result: One row per maximal run of three or more consecutively observed stockout days, ordered so the runs with the most forecast demand behind them surface first. run_length_days = run_end - run_start + 1 equals COUNT(DISTINCT snapshot_date) on every row by construction, since the island rule admits no internal gap, so that equality confirms the island logic and says nothing about data density. What it does catch is duplication: COUNT(*) above the span means a SKU-node-day was recorded more than once and its forecast_units were counted twice. Density shows up in the censored flag instead, as runs bounded by absent days rather than by observed available ones.

Verify it yourself

  • run_end - run_start + 1 equals COUNT(DISTINCT snapshot_date) on every row. Where COUNT(*) exceeds it, the partition has duplicate snapshots and forecast_units_lost is overstated for that run.
  • Runs for the same (sku_id, node_id) never overlap. Overlapping runs are the signature of the snapshot_date minus ROW_NUMBER key applied over duplicated SKU-node-days, which DENSE_RANK or a prior dedupe removes.
  • Before the three-day filter, the summed distinct-day counts across islands equal COUNT(DISTINCT (sku_id, node_id, snapshot_date)) over rows meeting the stockout condition.
  • Deleting one mid-run snapshot row on a test copy splits that run in two and can push both fragments under the three-day floor. The size of that shift is the exposure to snapshot outages, and it is why the censored flag ships beside the run length.
  • Runs touching the first or last day of the 90-day window are censored by the window itself and are flagged the same way as runs bounded by absent days.

Follow-up

  • A SKU was delisted mid-stockout, so was_listed flips to FALSE. Is the run over, and what does each choice do to the lost-demand number?
  • How would you extend this to partial-day unavailability using minutes_unavailable rather than the binary zero-stock test?
  • The same SKU is out at one node and in stock at another. Does that count as a stockout for the customer?

How would you approach predicting customer preferences for a new product?

Medium · product sense and metrics

Approach

  1. Fix the population and the time window before naming any metric.
  2. Restate the decision this analysis has to support, and who acts on the answer.
  3. 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?
  • What would you do if the primary metric and the guardrail moved in opposite directions?

If sales dropped unexpectedly, how would you investigate the cause?

Medium · product sense and metrics

Approach

  1. State what result would change your recommendation, so the answer is falsifiable.
  2. Name one primary metric, then the guardrail that stops it being gamed.
  3. Fix the population and the time window before naming any metric.

Follow-up

  • What would you do if the primary metric and the guardrail moved in opposite directions?
  • How would you detect that the metric is being gamed rather than genuinely improving?

What metrics would you use to evaluate the success of a product launch?

Medium · product sense and metrics

Approach

  1. Decompose the metric into the rates that drive it, and say which one you would check first.
  2. Restate the decision this analysis has to support, and who acts on the answer.
  3. Name one primary metric, then the guardrail that stops it being gamed.

Follow-up

  • How would you detect that the metric is being gamed rather than genuinely improving?
  • What would you do if the primary metric and the guardrail moved in opposite directions?

Given a dataset with sales data, how would you identify trends?

Medium · product sense and metrics

Approach

  1. Restate the decision this analysis has to support, and who acts on the answer.
  2. Fix the population and the time window before naming any metric.
  3. 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?

Describe how you would design an A/B test for a new product feature.

Medium · a/b testing and experimentation

Approach

  1. Say whether units interfere with each other, and switch design if they do.
  2. Decide the analysis before seeing data, including how long it runs and when you look.
  3. State the primary metric and the minimum effect worth shipping, then size the test.

Follow-up

  • What would you conclude if the result is positive but the test is underpowered?
  • How would you handle interference between treated and control units?

Describe a project where you used data to drive decision-making.

Medium

Approach

  1. Say what you would check first and why it is the highest-information step.
  2. Work from the decision backwards to the evidence you would need.
  3. Clarify what is being asked and what a complete answer would contain.

Follow-up

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

Score a monthly promotional event on incremental margin

Medium · promotions · pull-forward · incremental margin

Merchandising runs a sitewide weekend event every month. The current readout is event-week revenue against the prior week. You have fct_order_line (promotion_id, line_discount_cents, discount_funding, unit_paid_price_cents, unit_cost_cents, placed_at_utc, sku_id), fct_return_line and dim_sku. Deliverable: design the scorecard. Name the primary metric, the two effects the primary must be corrected for, how you charge the discount cost, and what has to be held back for the measurement to mean anything.

Approach

  1. Reject the week-over-week baseline outright. The event is scheduled onto a week chosen because demand was expected to be high, and it is compared against the week its own pull-forward drained. Both halves of the comparison are contaminated in the same direction.
  2. Set the primary as incremental contribution margin per exposed customer against a holdout, measured over a window extending past the event by at least one category repurchase interval, so pull-forward appears as a post-period deficit rather than being booked as lift.
  3. Correct for the second effect, cannibalisation, by reading at category level rather than promoted-SKU level. A discount on one SKU pulls units off its full-price neighbours, and a SKU-level read counts that transfer as creation.
  4. Charge the discount correctly. The depth is paid to every buyer, including those who would have paid full price, so incremental margin equals incremental units times unit margin at the promoted price, minus baseline units times discount per unit. Split by discount_funding, because supplier-funded depth does not consume retailer margin and should not be charged to the event.
  5. Name the holdback. A randomised customer holdout, or a geo split with pre-period parallelism checked. If the event genuinely cannot be withheld from anyone, say so and fall back to a synthetic control on comparable markets, and state plainly that the estimate is now assumption-dependent rather than measured.
  6. Net returns before reporting. Promoted units return at different rates than full-price units, so attribute refunds to the parent order's placed date and hold the read until the return window closes.

Worked solution (30 min)

  1. Define the holdout and verify pre-period parallelism on category revenue per customer for at least eight weeks before the event.
  2. Compute units and net merchandise revenue by arm for the event window and for a post-period of one category repurchase interval, refund-adjusted to placed date.
  3. Compute contribution margin by arm using unit_paid_price_cents less unit_cost_cents, less shipping and return costs, plus recovered_value_cents.
  4. Split SUM(line_discount_cents) by discount_funding and charge only the retailer-funded and shared portions to the event.
  5. Report event-window incremental margin and post-period incremental margin separately, then the sum, so pull-forward is visible rather than netted silently.

Expected result: Primary is incremental contribution margin per exposed customer over a holdout, read across event plus post-period, net of returns attributed to placed date, with discount cost split by funding source. Incremental units is the secondary, showing whether the event created demand or bought it. Both the event-window and post-period components are published, not just their sum.

Verify it yourself

  • Baseline units times retailer-funded discount per unit reconciles with the retailer-funded share of SUM(line_discount_cents) implied by the holdout baseline.
  • Post-period units in the promoted arm below the holdout indicates pull-forward, and that deficit must be netted off before any lift is claimed.
  • Category-level units must rise, not just promoted-SKU units; if promoted units rose and category units did not, the event moved sales sideways.
  • Return rate on promoted units versus full-price units in the same category, so the margin figure is not overstated by units still in transit back.

Follow-up

  • The event lifts revenue 22 percent and incremental contribution margin is negative. Walk me through exactly how that arithmetic works.
  • Supplier funding covers 60 percent of the depth. Does the event still lose money, and does that change what you recommend?
  • How long does the post-period window need to be, and what data did you use to choose that length?

Revenue reads twelve percent low after the daily load

Medium · pipeline failure · data quality · fx

The daily load finished and merchandise revenue for yesterday reads 12% below the same weekday last month. fct_order_line has order_line_id, order_id, placed_at_utc, placed_at_local, channel, fulfilment_node_id, quantity, unit_paid_price_cents, currency_code and fx_rate_to_usd. You have 30 minutes before the morning review. Deliverable: decide whether this is a partial or corrupted load or a real demand drop, list the checks you ran in the order you ran them, and separate what you would page now from what you would wait on.

Approach

  1. Count rows before summing money. Order lines per hour by channel and by fulfilment_node_id against the same weekday for the last four weeks. A partial load shows as a clean truncation at an hour boundary or as one source system contributing exactly zero; a demand drop is a proportional sag across hours.
  2. Audit the currency path separately, because it can deflate revenue while every row count is correct. Total quantity * unit_paid_price_cents per currency_code in native units, then in USD, and compare the implied fx_rate_to_usd per currency against the reference rate. A stale or defaulted rate is a silent multiplier, not an error.
  3. Distinguish missing from late. Channels like store_pos, call_centre and subscription_autoship land on different batch schedules than web, so re-run the same partition counts a few hours later and compare like for like before calling anything real.
  4. Split by placed_at_local hour and by country to tell a regional outage from a load failure: an outage has a geographic and temporal shape, a load failure has a system shape.
  5. Only when counts and FX both reconcile, treat the movement as demand and hand it to the funnel decomposition. Page the pipeline owner for anything in the first two checks; wait for the batch window before restating anything in the third.

Follow-up

  • The counts reconcile and FX is fine, but revenue is still down 5%. What are the first three cuts you take on the demand side?
  • How would you build a check that catches a defaulted fx_rate_to_usd before the dashboard refreshes, and what does it alert on?
  • You restated the number at 09:00 and the late batch arrived at 11:00. What do you owe the people who already read it?

Suggested practice checklist

Four days spend equal time on query work, statistics, modelling and product judgement at deliberately shallow depth, which produces a scored map of where you actually stand. The last three days spend everything on the two areas the role weights most, and close by re-running day one to measure movement.

Day 1 — Breadth pass: query fluency

  • Solve six prompts spanning aggregation, joins, window functions and date arithmetic in 60 minutes total, stopping at 10 minutes each whether or not it works, and mark every prompt as solved, solved slowly, or stuck.
  • For each unsolved prompt write the single blocking sentence (I lost the grain, I did not know the frame clause, I could not express the date boundary) instead of reading the solution.
  • Translate one pandas transformation you know well into SQL and one SQL query into pandas, checking that both return the same row count and the same totals.

Deliverable: A scored six-row table, one line per prompt, saved for the day-seven re-run.

Day 2 — Breadth pass: statistics and inference

  • Answer ten short questions in writing with nothing open: what a p-value is conditional on, what a 95 percent interval covers across repeated samples, when a paired test is the right one, what the bootstrap estimates, why multiple comparisons inflate false positives, how controlling the family-wise error rate differs from controlling the false discovery rate, what power depends on, what a missed real effect costs a product, the three situations where the central limit theorem does not rescue you (small n, very heavy tails, dependent observations), and what a standard error is the standard deviation of.
  • Grade yourself against a reference and count only the answers that were exactly right, not the ones that were nearly right.
  • Rewrite the two weakest answers the following morning from memory in full sentences.

Deliverable: Ten graded answers with an honest count of exact hits.

Day 3 — Breadth pass: modelling

  • Take one tabular dataset end to end in 90 minutes: a leakage-safe split, a baseline that is not a model (majority class or historical mean), one regularized linear model, one gradient-boosted tree, and a single evaluation metric chosen before you look at any result.
  • Write why that metric fits the cost structure: precision at a fixed recall for alerting, calibration for anything feeding a price or a threshold, ranking metrics for retrieval, and note that area under the ROC curve is insensitive to class balance in a way that can flatter a rare-positive problem.
  • Name the leak you were most likely to introduce (an encoding fit on all rows before splitting, or a feature computed after the label's timestamp) and write the check that would have caught it.

Deliverable: A notebook whose first cell states the metric and the baseline, plus two lines on what beat what and by how much.

Day 4 — Breadth pass: product judgement

  • Answer three case prompts aloud at 15 minutes each, timing how long passes before you state a success metric.
  • For one case write the first segmentation you would run and the row counts you expect per segment, so that a tiny segment cannot quietly drive the conclusion.
  • Take a metric definition you did not write, from a public dashboard, a textbook, or documentation you already have open, and list every place two analysts implementing it would diverge: which rows the denominator admits, whether the unit is an account or a person, what the time window is anchored to, and what happens to data that arrives late. Then write the one question that would close the largest of those gaps.

Deliverable: Three recorded case answers plus an ambiguity list for a metric someone else defined, ending in the single question you would ask about it.

Day 5 — Depth, first area

  • Rank the four areas by how many bullet points in the role description each one covers, pick the top one, and spend the entire day inside it.
  • Work the six hardest problems you can find in that area and for each write the generalizable move you should have reached for first, rather than the answer.
  • Re-solve the two you failed the same evening with notes closed.

Deliverable: Six generalizable moves written as instructions to yourself, not as solutions.

Day 6 — Depth, second area, and the seam between them

  • Repeat the depth protocol on the second-ranked area with the same six-problem structure.
  • Construct one problem that requires both areas at once, for example a metric redefinition whose effect you must validate with a test whose readout you then have to query.
  • Solve your own combined problem end to end and note where the handoff between the two areas cost you time.

Deliverable: One combined problem, solved end to end, with the handoff failure written down.

Day 7 — Integration and re-measurement

  • Re-run the six prompts from day one under the same clock and compare both correctness and time.
  • Run a 60-minute mixed mock that moves between areas without warning, since switching cost is what breadth passes do not train.
  • Write the two areas you would still fail on, and the sentence you will use in the interview when you hit one of them.

Deliverable: A before-and-after score table plus a written plan for the two remaining gaps.

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.

What motivates you to work in the plant-based food industry?

Medium · behavioural and stakeholder questions

Approach

  1. Quantify the outcome, including what you would not claim credit for.
  2. Pick a story where you drove the decision, not one where you observed it.
  3. Name the disagreement or constraint, and how you resolved it with evidence.

Follow-up

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

Describe a time when you had to influence others to adopt a data-driven decision.

Medium · behavioural and stakeholder questions

Approach

  1. Pick a story where you drove the decision, not one where you observed it.
  2. Name the disagreement or constraint, and how you resolved it with evidence.
  3. Close with what you would do differently, concretely.

Follow-up

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

Defend a promotion read that contradicts the revenue number

Medium · incrementality · pull-forward · stakeholder pushback

You analysed a site-wide twenty-percent weekend event. Revenue over the event week rose eleven percent year over year, contribution margin per delivered order fell, and the two weeks after the event ran below the pre-period baseline. The merchandising lead who owns the event calendar has already circulated the revenue figure. You have ten minutes at a planning meeting attended by that lead and their director. Deliver the finding and the recommendation you would make for the next event, and describe how you handle the pushback in the room.

Approach

  1. Before the meeting, write the finding as one sentence about the event, not about the person who owns it: the event moved price and timing, not demand. Keeping the event owner's judgement out of the sentence is what keeps the holdout you want on the table.
  2. Open with the number they already have, then decompose revenue into traffic, conversion, units per order, net price per unit and keep rate, and show which term moved. If price per unit fell and units per order held, the revenue gain was volume bought at a lower price.
  3. Quantify the post-period dip as the pull-forward estimate, and state the measurement window explicitly. The window has to extend past the event because the demand was borrowed from the weeks after it.
  4. Convert to incremental contribution margin. At gross margin m on list price and discount depth d, holding contribution flat requires a unit lift of d / (m - d), valid when m > d, when the discount applies to every unit sold, when unit cost is constant, and when nothing was cannibalised from full-price SKUs. Compare the observed lift to that threshold.
  5. Say what would change your mind and propose the design that would settle it: a geo or customer holdout on the next event, with the number of units and weeks it needs.
  6. End on the next event, not a verdict on the last one, so the meeting produces a decision rather than a defence.

Follow-up

  • The lead says the event acquired new customers whose lifetime value justifies the margin loss. How would you test that claim with dim_customer and fct_order_line?
  • Your director wants one number to take to finance. Which one, and what caveat travels with it?