Interview guides · ExxonMobil
ExxonMobil Data Scientist Interview Questions & Guide 2026
Data scientist interview prep for ExxonMobil: original practice on experiment design, SQL and metric reasoning, with worked solutions.
The 60-second brief
As a Data Scientist at ExxonMobil, you play a pivotal role in harnessing data to drive insights, optimize operations, and inform strategic decisions within the organization. You will utilize advanced analytics and machine learning techniques to analyze complex datasets, ultimately impacting how ExxonMobil operates across various sectors, from upstream development to commercial supply chains. This role is critical in ensuring that the company remains competitive in a rapidly evolving energy landscape.
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
- Evaluate forecasts at the lag ordering actually uses
- Price a service-level change in working capital
- Join daily snapshots to shipment events safely
Overview
Your contributions will directly influence the efficiency of production processes, enhance the accuracy of forecasting models, and support decision-making across diverse teams. You will engage with complex datasets related to resource allocation, supply chain logistics, and market dynamics, providing insights that inform vital business strategies. The complexity and scale of the challenges you address make this position both exciting and essential to the future of ExxonMobil’s operations.
The ExxonMobil interview process
ExxonMobil candidates report 3 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.
Initial 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 Assessment
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
Behavioral Interview
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
Scoring intermittent demand with MAPE
MAPE is undefined whenever the actual is zero, which is most days for a C-class item at a forward-stocking location, and it is asymmetric even where it is defined: under-forecasting is bounded at 100 percent error while over-forecasting is unbounded. Optimising it therefore drives systematic under-forecasting on exactly the items whose stockouts are most expensive, and the bias is invisible in the headline because the zero-actual rows were dropped before averaging. Use WMAPE (sum of absolute errors over sum of actuals), a scaled error such as RMSSE, or a pinball loss at the service quantile the policy targets, and always state the aggregation level and forecast lag, because the same series scores very differently at daily SKU-store level and weekly SKU-region level.
Attributing demand variability to the node where it is observed
Order variance amplifies as it moves upstream: batching to a truckload, minimum order quantities, forecast-driven ordering and promotional pull all convert smooth end demand into lumpy replenishment orders, so a plant can see a coefficient of variation several times that of the underlying consumption. Diagnosing the plant's schedule instability as a plant problem then produces interventions that cannot work, because the generating process sits one or two echelons downstream. Measure the bullwhip ratio explicitly (variance of orders placed by a node over variance of demand it received) at each echelon, and fix the ordering rule at the node where the ratio jumps rather than the node where the pain is felt.
Over-explaining the method and under-explaining the implication
Lead with the answer and what you would do about it, then give the approach when asked. Roughly one sentence of method per three of implication is the right ratio for a stakeholder-facing answer; the interviewer already knows what a regression is.
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.
Questions & practice
How would you implement a decision tree algorithm from scratch?
Medium · machine learning and modelling
Approach
- 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.
- Pick an evaluation metric that matches the cost of each error type, not a default.
Follow-up
- How would you choose the decision threshold, and who owns that choice?
- What would you monitor after launch to know the model is still valid?
Weighted forecast error at the lag ordering actually consumes
Easy · wmape · forecast bias · aggregation grain · pandas
You are given fct_inventory_daily as a DataFrame with inventory_date, sku_id, location_id, demand_qty and forecast_qty_lag7. Replenishment is decided weekly per SKU per node, so score the forecast at that grain over a trailing 8 weeks. WMAPE is the sum of absolute weekly errors over the sum of weekly demand; weighted mean percentage error is the same ratio with the error left signed. forecast_qty_lag7 is null before a series existed. Return one row per location_id carrying both statistics, the shared denominator, and the count of rows excluded. No forecasting library.
Approach
- Decide the null rule before writing any aggregation: a sku-location-week where any constituent day has a null forecast must be dropped whole. Signed error here is forecast minus demand, so summing 7 days of demand against the 5 days of forecast that survived leaves the numerator short by two days of forecast and drives the ratio negative, which reads as systematic under-forecasting the model never committed.
- Aggregate demand_qty and forecast_qty_lag7 to sku x location x week first, then difference. Differencing daily and summing the absolute values afterwards answers a different question, since intraday timing error cancels inside a week and the ordering decision never sees it.
- Build week buckets from inventory_date with a fixed anchor (a Monday-start ISO week or a rolling 7-day offset), and use the same buckets for both series so no partial week sits at either end of the 8-week window.
- Compute the two statistics by summing numerator and denominator to the location level and dividing once. Never average sku-level ratios: a C-class item with 3 units of weekly demand would otherwise carry the same weight as an A-class item with 3,000.
- Return the denominator and the excluded row count as columns, not as a printed aside, so a reader can tell a genuinely accurate node from one with almost no scored history.
Worked solution (20 min)
- Filter to the trailing 8 complete weeks by inventory_date, then drop any sku-location-week containing a null forecast_qty_lag7 and record how many rows that removed.
- Group by sku_id, location_id, week and sum demand_qty and forecast_qty_lag7 into weekly totals.
- Add abs_err = (forecast - demand).abs() and signed_err = (forecast - demand) on the weekly frame.
- Group by location_id and sum abs_err, signed_err and demand_qty; divide the first two by the third to get wmape and wmpe.
- Attach the denominator and the excluded count, and sort by denominator descending so the nodes that matter read first.
Expected result: One row per location_id with wmape, wmpe, demand_denominator and excluded_rows. wmape is non-negative; wmpe carries a sign, positive meaning the node is over-forecast under the forecast-minus-demand convention and negative meaning under-forecast. A node with wmpe near zero and wmape near 0.3 is unbiased but inaccurate.
Verify it yourself
- Recompute WMAPE at daily grain over the identical row set: it must be greater than or equal to the weekly figure, because the absolute value of a sum of daily errors never exceeds the sum of their absolute values.
- Recompute the signed bias at daily grain: it must equal the weekly figure exactly, since signed errors and the denominator both add across days. A mismatch means the two grains are not covering the same rows.
- Confirm abs(wmpe) is less than or equal to wmape at every location; a violation means the two statistics are being computed over different row sets, which is the signature of a partially-dropped week.
- Set forecast_qty_lag7 equal to demand_qty on a copy and confirm both statistics return exactly 0.0.
Follow-up
- The same series is scored at lag 28 and WMAPE roughly doubles. Is that a model problem or an expected property of the horizon?
- A node shows WMAPE of 0.35 and bias of 0.01. What can you and can you not conclude about its inventory position?
- How would you report accuracy for a SKU whose weekly demand is zero in 40 of the 52 weeks?
As-of join snapshots to order lines without merge_asof
Medium · as-of join · fan-out · searchsorted
You have fct_order_line (order_line_id, sku_id, ship_from_location_id, actual_ship_at_utc, shipped_qty, shipment_id) and fct_inventory_daily (inventory_date, sku_id, location_id, available_qty). For every shipped line, attach available_qty from the latest snapshot strictly before the ship date for that exact sku and node, plus the shipment's delivery timestamp from fct_shipment_leg (leg_id, shipment_id, delivered_at_utc, leg_status). The output must have exactly one row per input order line. pandas.merge_asof is not available; implement the as-of match yourself.
Approach
- Collapse fct_shipment_leg to one row per shipment_id before joining anything. A shipment is one or more legs, so a direct line-to-leg join multiplies every line by its leg count and quietly inflates every sum computed afterwards.
- Define the shipment-level delivery as the maximum delivered_at_utc across its legs, and set it null if any leg is not delivered, since the customer has the order only when the last leg lands.
- Sort the snapshot frame by (sku_id, location_id, inventory_date) and the line frame by the same key plus ship date, then use np.searchsorted with side='left' on the sorted dates within each key group to find the insertion point; the matching snapshot is the row at position minus one.
- Guard the boundary explicitly: an insertion point of 0 means no snapshot exists before that ship date, and the result must be null rather than the first available future snapshot. Count those lines and report them.
- Assert the output row count equals the input line count immediately after each merge, before any aggregation. Row-count drift is the only symptom a silent fan-out produces, and it is invisible once the frame has been grouped.
Follow-up
- The snapshot is taken at the node's local end of day and actual_ship_at_utc is UTC. Where does that break the strictly-before rule and what do you need from dim_location to fix it?
- How would you extend this to allocate each leg's linehaul cost back to the order lines it carried?
- What changes if a line ships in two partial shipments?
Write a function to calculate the mean and median of a dataset.
Medium · sql and data manipulation
Approach
- Say which table is the grain you start from, and join outward from it.
- 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.
Follow-up
- How would you verify this result without re-running the same query?
- How does the query change if the join becomes one-to-many?
Describe how you would optimize a SQL query for better performance.
Medium · sql and data manipulation
Approach
- 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.
- Compute rates by summing numerator and denominator separately, never by averaging rates.
Follow-up
- What breaks if events arrive late or out of order?
- How does the query change if the join becomes one-to-many?
Dead stock cells excluding SKUs used as substitutes
Medium · anti-join · null semantics · dead stock
Find dead stock: sku_id x location_id cells whose latest fct_inventory_daily row has on_hand_qty > 0 and which have no fct_order_line with that sku_id and ship_from_location_id in the last 90 days. Also exclude any SKU that filled another line as a substitute in that window; fct_order_line.substituted_sku_id is NULL on every line that was not substituted. Value each cell at on_hand_qty * standard_cost_cents taken from the snapshot, not from dim_sku. Return the cell, days since its last demand line, and value, ordered by value. Treat line_status = 'cancelled_no_supply' as demand.
Approach
- Take the latest snapshot per sku-location with ROW_NUMBER() OVER (PARTITION BY sku_id, location_id ORDER BY inventory_date DESC) = 1, or an equivalent MAX(inventory_date) join, and keep rows with on_hand_qty > 0.
- Anti-join demand with NOT EXISTS against fct_order_line on both sku_id and ship_from_location_id over the 90-day window, counting every line status including cancelled_no_supply, because unmet demand is still demand and a cell with it is not dead.
- Write the substitute exclusion as NOT EXISTS, or force substituted_sku_id IS NOT NULL inside the subquery. NOT IN over a column that contains NULLs evaluates to NULL for every candidate that is not an explicit match, so the predicate is never true and the result set is empty.
- Value from the snapshot's standard_cost_cents rather than dim_sku, since the dimension carries only the cost in force today and is revised at period close without back-dating.
- Get days since last demand from a LEFT JOIN LATERAL MAX(ordered_at_utc), and report cells that never had a line in a separate bucket instead of as a very large number.
Worked solution (30 min)
- CTE latest_snap: ROW_NUMBER over sku_id, location_id ordered by inventory_date DESC, filtered to rn = 1 and on_hand_qty > 0.
- CTE last_demand: MAX(ordered_at_utc) grouped by sku_id, ship_from_location_id over all of fct_order_line, with no status filter.
- Select from latest_snap LEFT JOIN last_demand, keeping rows where the max is NULL or older than 90 days.
- Add AND NOT EXISTS (SELECT 1 FROM fct_order_line o WHERE o.substituted_sku_id = latest_snap.sku_id AND o.ordered_at_utc >= now() - interval '90 days').
- Project value_cents = on_hand_qty * standard_cost_cents, order by it descending, and run the same query once with NOT IN to see the empty result for yourself.
Expected result: One row per dead sku-location cell with value_cents computed from the latest snapshot's own cost column, days_since_last_demand (or an explicit never-ordered bucket), ordered by value_cents descending. The substitute exclusion removes some rows; it must not empty the result.
Verify it yourself
- Swapping NOT EXISTS for NOT IN on substituted_sku_id collapses the output to zero rows. That is the diagnostic for the NULL semantics, not a data finding.
- Total value across returned cells is strictly less than total on-hand value at the same snapshot date.
- No returned cell appears in SELECT DISTINCT sku_id, ship_from_location_id FROM fct_order_line over the same 90 days.
Follow-up
- The query returns zero rows. Name the two likeliest causes and say which you would test first, with the one-line test.
- How do you separate genuinely dead stock from a seasonal item out of season and from a lifecycle_status = 'pre_launch' SKU?
- The same SKU is dead at one node and short at another. Write the query that finds that pattern and say what it implies about the policy.
How would you approach predicting future oil prices using historical data?
Medium · product sense and metrics
Approach
- Restate the decision this analysis has to support, and who acts on the answer.
- Fix the population and the time window before naming any metric.
- State what result would change your recommendation, so the answer is falsifiable.
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 your tasks when working on multiple projects?
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.
- Name one primary metric, then the guardrail that stops it being gamed.
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 steps would you take to ensure the reliability of your data analysis?
Medium · product sense and metrics
Approach
- Restate the decision this analysis has to support, and who acts on the answer.
- 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.
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?
Given a dataset of energy consumption, how would you identify patterns and trends?
Medium · product sense and metrics
Approach
- Restate the decision this analysis has to support, and who acts on the answer.
- Decompose the metric into the rates that drive it, and say which one you would check first.
- Fix the population and the time window before naming any metric.
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 structure an A/B test for a new product feature.
Medium · a/b testing and experimentation
Approach
- State the primary metric and the minimum effect worth shipping, then size the test.
- Name the randomisation unit first; it decides the variance and what the test can detect.
- Say whether units interfere with each other, and switch design if they do.
Follow-up
- What would you do if you could not randomise at all?
- What would you conclude if the result is positive but the test is underpowered?
Explain the difference between supervised and unsupervised learning.
Medium
Approach
- State your assumptions explicitly before working the problem.
- Clarify what is being asked and what a complete answer would contain.
- Work from the decision backwards to the evidence you would need.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
Recover the effect of expediting from an automatic threshold rule
Hard · regression discontinuity · fuzzy rd · late · manipulation test
The transport system upgrades service_level to 'expedite' whenever projected slack at tender falls below 12 hours, and charges expedite_premium_cents for it. Projected slack is promised_delivery_at_utc minus the planned arrival implied by planned_transit_hours, and it is recoverable from two years of fct_shipment_leg. Finance wants to know what the premium buys. No randomised holdout is possible, and the rule has run unchanged throughout. Estimate the causal effect on on-time delivery, state which shipments your estimate applies to, and give the checks that would invalidate it.
Approach
- Recognise the design: treatment is a deterministic function of an observed continuous running variable with a known cutoff, so a regression discontinuity identifies the effect at that cutoff with no randomisation, provided nothing other than the expedite rule changes at 12 hours.
- Estimate with local linear regression on each side of the cutoff, a triangular kernel and an MSE-optimal data-driven bandwidth, reporting robust bias-corrected confidence intervals. Do not fit a global high-order polynomial: it imports weight from observations far from the cutoff and manufactures jumps that are artefacts of the functional form.
- Test manipulation, because planners can edit the promise date and that moves slack directly. Run a density test of the running variable at the cutoff and inspect the histogram for heaping just below 12 hours. If slack is recorded to the whole hour, treat the running variable as discrete, cluster by its value, and consider a donut estimate that drops the bins adjacent to the cutoff.
- Test continuity of everything that is not the treatment: weight_kg, cube_m3, mode, stop_count, lane and carrier should not jump at 12 hours. Add placebo cutoffs at 8 and 16 hours, where the effect should be indistinguishable from zero.
- Handle overrides. If dispatchers sometimes expedite above the cutoff or decline below it, the design is fuzzy: estimate the jump in the probability of expediting and divide the outcome jump by it, equivalently two-stage least squares with the below-cutoff indicator as the instrument, and report the first-stage jump, because a weak first stage makes the ratio unstable.
- Finish with the arithmetic Finance asked for: the jump in on-time probability at the cutoff against mean expedite_premium_cents per unit inside the bandwidth, expressed as cost per incremental on-time delivery and labelled as applying only near 12 hours of slack.
Worked solution (40 min)
- Reconstruct the running variable at tender time from tendered_at_utc, planned_transit_hours and promised_delivery_at_utc, then verify it reproduces the observed expedite assignment on both sides of 12 hours.
- Plot binned means of on-time delivery against slack with a data-driven bin width, and look at the picture before fitting anything.
- Fit local linear RD with a triangular kernel and MSE-optimal bandwidth; report the estimate with robust bias-corrected intervals plus sensitivity at half and double the bandwidth.
- Run the density test at the cutoff, the covariate-continuity checks, and placebo cutoffs at 8 and 16 hours.
- Convert to cost per incremental on-time delivery using mean expedite_premium_cents per unit within the bandwidth.
Expected result: A local average treatment effect on on-time delivery for shipments with projected slack near 12 hours, reported with robust bias-corrected intervals, accompanied by the first-stage jump if the rule turns out to be fuzzy, and converted to a cost per incremental on-time delivery from the premium actually paid inside the bandwidth.
Verify it yourself
- The assignment check should be near-deterministic; if a material share of legs above 12 hours are expedited anyway, the design is fuzzy and the sharp estimate is wrong by the size of the first stage.
- The estimate should be stable when the bandwidth is halved and doubled; a sign flip means the functional form is driving the result.
- Placebo cutoffs at 8 and 16 hours should return effects indistinguishable from zero.
- Any jump at the cutoff in weight, cube, mode mix or carrier share means something besides the expedite rule changes at 12 hours, and the design fails.
Follow-up
- The jump is 4 points at the cutoff. What would you need before recommending the threshold move to 18 hours?
- Slack is recorded to the whole hour and 9% of legs sit exactly at 12. What does that heaping do to the estimate, and how do you handle it?
Plant schedule instability generated two echelons downstream
Hard · bullwhip · echelon attribution · variance
A plant reports that the replenishment orders it receives swing far more than end-customer demand, and asks for a larger finished-goods buffer. You have fct_order_line for customer demand at store and forward-stock nodes, fct_inventory_daily (on_order_qty, demand_qty per sku-location-date), dim_location (echelon, parent_location_id) and dim_sku (units_per_case, cases_per_pallet). Measure amplification at each echelon, identify the node whose ordering rule generates it, and price the buffer request against fixing that rule. Deliverable: a per-echelon amplification table and a recommendation.
Approach
- Build two series per node at a fixed weekly bucket per SKU: the quantity the node ordered from its parent_location_id, and the demand it received from its children or from customers. Both series must cover the same SKU, the same weeks and the same node set, or the ratio between them means nothing.
- Compute amplification per node as Var(orders placed) / Var(demand received), and report the squared coefficient-of-variation form alongside it. Mean volumes differ by an order of magnitude between echelons, so a raw variance ratio is not comparable across nodes of different size while the CV-squared ratio is scale-free.
- Read the table echelon by echelon and find where the ratio steps up. The generating node is where it first exceeds one by a wide margin, not the node that feels the pain: amplification observed at the plant is the product of every ratio below it along that chain.
- Name the mechanism at that node rather than asserting bullwhip generically. Minimum order quantity and lot-size multiple, the review period, batching up to a full truckload, and forecast-driven ordering that reacts to its own safety stock recalculation each leave a distinct signature in the order-size histogram.
- Price both options rather than choosing one. The buffer request converts to units, then to cost at standard_cost_cents and to carrying cost at the applicable cost of capital. The rule change converts to more frequent, smaller replenishment and therefore more freight per unit. Present the trade with both numbers attached.
Follow-up
- Orders cluster on exact truckload multiples. Would you break the batching rule, and what does that cost in freight per unit?
- How would you test an ordering-rule change when there are twelve nodes, they draw on the same stock and the same outbound loads, and one replenishment cycle is three weeks?
- Part of the plant's variance is its own production batching. How do you separate inbound amplification from what the plant creates for itself?
Suggested practice checklist
For someone who can already write the query and train the model but stalls when asked what to measure or whether a change is worth making. Metric definition and case structure come first; the technical work is kept as maintenance rather than the centre of the week.
Day 1 — Metric anatomy
- For three products you use daily, write one primary metric, two input metrics that plausibly move it, and one guardrail that would catch a cheap way of moving the primary at the cost of the product.
- For one of them, specify the metric precisely enough that two analysts would return the same number: numerator, denominator, unit of observation, time window, and how returning and deleted accounts are treated.
- Pick a ratio metric and write what happens to it when the denominator shrinks for reasons unrelated to the numerator, with a concrete example of that happening.
Deliverable: A one-page metric tree for one product, with the primary metric written as an unambiguous spec.
Day 2 — Diagnosing a drop without guessing
- Take the prompt "weekly active users fell 8 percent week over week" and write the segmentation plan before proposing any cause: platform, region, tenure cohort, acquisition channel, and whether the movement sits in the numerator or in a changed denominator.
- List the instrumentation failures that manufacture fake drops (a client release that stopped firing an event, a bot filter change, a shifted date boundary or timezone) and write the query that rules out each one.
- Rehearse stating the boring explanations first, seasonality and day-of-week composition, before reaching for a product cause.
Deliverable: A drop-diagnosis checklist short enough to recite from memory in under a minute.
Day 3 — Should we build it
- Take a feature idea and write it as a bet: what you believe is true, what would have to be true for it to pay off, the metric that would confirm it, and the effect size that would justify the engineering cost.
- Size the opportunity top-down and bottom-up, then reconcile the two numbers in writing instead of quoting whichever is friendlier.
- Write the counter-metric that would make you kill the feature even if it wins on the primary metric.
Deliverable: A one-page product memo ending in a decision rather than a list of considerations.
Day 4 — The places aggregate numbers lie
- Construct a Simpson's paradox numerically: two segments where the treatment wins within each segment yet loses overall, and identify the shift in segment weights that causes it.
- Take a heavy right-tailed quantity such as revenue per user and write why the mean is the wrong summary, which percentile you would report instead, and what a moving mean with a stable median tells you.
- Write your definition of a session for the product from day one, then name two real behaviours it misclassifies.
Deliverable: One page holding a worked Simpson's paradox table and a session definition with its two known failure cases.
Day 5 — Technical maintenance, aimed at metrics
- Solve four timed SQL prompts that all end in a ratio metric, so the question of grain stays live in every answer.
- Compute a 95 percent confidence interval for a proportion on a small sample, and state why the normal approximation is unreliable when either np or n(1 minus p) falls below roughly 10, along with which interval you would use instead.
- Take one metric from your day-one tree, write the query that computes it correctly, then write the query that computes it wrong in the most plausible way and explain how you would notice.
Deliverable: Four solved prompts plus a matched correct and plausible-wrong query for one metric.
Day 6 — Turning engineering work into data science stories
- Write three project stories as situation, decision, trade-off, outcome, each carrying one number and one thing you got wrong.
- For the story you will lead with, prepare an answer to "what would you do differently" that names a decision you made, not a constraint you were handed.
- Practise the sentence that reframes a systems project as a question project: the question the work answered, ahead of the pipeline it shipped.
Deliverable: Three written stories with the lead story delivered aloud and timed under four minutes.
Day 7 — Mock case and gap list
- Run a 40-minute mock case with someone playing a product manager who pushes back on your metric choice, and record it.
- Listen back and mark every moment you proposed a solution before the success metric existed.
- Rewrite those moments as the question you should have asked, and rehearse the first 90 seconds of the case until scoping comes before solving.
Deliverable: A recorded case plus a rewritten opening 90 seconds.
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.
Tell me about a time when you had to communicate complex data findings to a non-technical audience.
Medium · behavioural and stakeholder questions
Approach
- Quantify the outcome, including what you would not claim credit for.
- Name the disagreement or constraint, and how you resolved it with evidence.
- Close with what you would do differently, concretely.
Follow-up
- What did you decide not to do, and why?
- What would you do differently if you ran that project again?
Walk through an inventory analysis that turned out wrong
Medium · error postmortem · snapshot bias · inventory turns
Six months ago you published inventory turns by node using the month-end on_hand_qty snapshot from fct_inventory_daily as the denominator, and two DCs cut cover on the strength of it. A finance review later showed month-end is systematically the lowest on-hand point of the month, because shipments cluster before close, so your turns were overstated and days of supply understated. Tell the story: what you built, how the error surfaced, what it cost, what you corrected, and the control that now prevents this class of mistake rather than this specific instance.
Approach
- Give the facts in order and own the decision, not just the code: you chose the month-end snapshot because it was one row per sku-location and fast, and you did not check whether the sampling point was representative.
- Quantify the error rather than describing it: recompute the same window against the average of every daily snapshot and state the gap in turns and in days of supply, which in a network with close-period push typically runs ten to twenty percent.
- Separate the consequence from the mistake honestly: say what the two DCs did, whether service actually degraded, and if it did not, say so instead of inflating the damage to sound accountable.
- Describe the correction and the notification: who was told, how quickly, and whether the restated number changed the recommendation.
- Close on the generalised control: a denominator convention written into the metric definition, plus a row-count assertion at each join grain, because the same shape of error appears when a daily snapshot is joined to shipment events on date equality and one shipment with several legs fans the snapshot out.
Follow-up
- How did you decide whom to tell first, and how did you phrase it?
- What made you trust the month-end snapshot in the first place, and what would have caught it in review?
Prioritise transport, plant and commercial in one week
Medium · prioritisation · stakeholder management · decision value
Three requests land in the same week. Transport wants landed cost per delivered unit split by lane and mode from fct_shipment_leg for a carrier bid that closes in nine days. A plant manager wants OEE on two work centres decomposed into availability, performance and quality from fct_production_run for a capital request due next month. Commercial wants a fill-rate root cause from fct_order_line for an account review on Thursday. You are the only data scientist and have about four working days. Give your order, the rule that produced it, and what you say to the two who wait.
Approach
- Rank by the decision each request feeds and by whether your input can still change it, not by seniority or by who asked loudest: a bid closing in nine days is a live decision with a large irreversible spend attached, a capital request due next month has slack, and an account review has a fixed date but a smaller reversible decision.
- Check reversibility and blast radius: carrier rates lock for a contract period across every lane, so an error or an absence there is expensive for a year, while the fill-rate story can be revised next week.
- Look for the cheap partial that unblocks someone else: the fill-rate cut by node, by short_reason_code and by week is a few hours of work and covers most of what commercial needs on Thursday, so it does not have to wait behind the bid.
- Sequence with explicit time boxes: the commercial cut first because it is short and date-locked, the lane and mode cost analysis next with the allocation basis for multi-stop loads stated up front, and the OEE decomposition scheduled into the following week with a date the plant manager can hold you to.
- Tell the plant manager directly and early, with a date rather than a maybe, and say what you need from him in the meantime so the delay produces something.
- Escalate the trade-off rather than absorbing it silently: your manager should know a capital request slipped a week, because that is a business choice and not yours alone to make.
Follow-up
- The plant manager escalates and his director asks you to reorder. What do you do?
- Which of the three would you push back on entirely, and what would you offer instead?