Interview guides · Gordon Food Service
Gordon Food Service Data Scientist Interview Questions & Guide 2026
Gordon Food Service Data Scientist interview guide: reported rounds, practice questions with worked solutions and a 7-day prep plan.
The 60-second brief
A Data Scientist at Gordon Food Service operates at the intersection of complex supply chain logistics, customer behavior, and large-scale food distribution data. You will be responsible for transforming raw data into actionable insights that optimize how one of the largest food service distributors in North America operates. Your work directly influences efficiency, inventory management, and the personalization of client experiences in a fast-paced, high-volume environment.
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
- Decompose a metric move by segment and mix
- Define numerator, denominator and window precisely
- Pick a randomisation unit that respects interference
Overview
This role is both technically demanding and strategically significant. You will often work alongside cross-functional teams, including engineering, product managers, and operations, to solve real-world problems such as demand forecasting, route optimization, and pricing strategies. Success in this position requires a balance of rigorous analytical modeling and the ability to clearly communicate your findings to non-technical stakeholders who rely on your data to make high-stakes business decisions.
Candidates report a consistently positive experience, noting that the team is professional, welcoming, and maintains a clear, efficient communication style throughout the hiring process.
The Gordon Food Service interview process
Gordon Food Service candidates report 3 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.
Recruiter Screen
A screening call is a matching exercise run by someone who will not evaluate your statistics. They are checking that the work described on your resume is work you personally did, and that its scope matches the level the role is written for. Logistics get settled in the same half hour so nobody spends an interviewer's afternoon on a mismatch. The answer that fails is the one narrated in the plural. If every sentence is 'we built' and 'the team decided', there is nothing specific to write down about you. Name the piece that was yours, the decision you made inside it, and what changed after.
What to demonstrate
- Whether the ownership implied by your resume survives one round of follow-up about who actually did which part
- Whether your described scope (data size, stakeholders, what shipped) matches the seniority the role is written at
- Whether timeline, location and compensation expectations make the rest of the loop worth scheduling
How to prepare
- Rewrite your top three resume bullets in the first person singular, each with the decision you made and what moved afterwards, then say them out loud once so the 'we' does not return under pressure
- Attach one number to each project: the baseline, the change, and the window it was measured over. Where impact was never measured, say that plainly rather than inventing a figure
- Settle your compensation range before the call and give it as a range with a reason behind it, such as current total comp or a competing timeline, instead of deflecting the question twice
Hiring Manager Interview
This conversation decides whether you can be handed a problem nobody has finished defining and left alone with it for a few weeks. The manager is listening for how you behave when the brief is thin: what you clarify before starting, and what you settle on your own rather than escalating. Most candidates over-index on technical depth here and under-describe the decisions they actually owned. Say who wanted the work, what you chose not to do, and where you would have stopped and asked. A clean account of your own judgement carries this round further than a longer project list.
What to demonstrate
- Whether you can name a decision that was yours alone, as opposed to one the team arrived at
- How you respond to a request that arrives with no success metric attached to it
- Whether the effort you estimate for a piece of work matches the work you just described doing
- What you escalate, and how long you sit on a problem before you do
How to prepare
- For each project you plan to raise, write one sentence saying what would not have happened if you had not been on it, and check that the sentence is about an outcome rather than an artefact
- List the decisions in your last project that were genuinely yours, and for each one write down the option you rejected and why
- Prepare the project that went badly: the point at which you knew, who you told, and what it cost before it was caught
Team Interview
An added round often puts you in front of someone outside the core hiring team: a partner engineer, a product owner, a domain expert, sometimes a more senior manager. The question they are really asking is not whether you can do the work but whether they would trust a number that came from you. That changes what a good answer looks like. Lead with what the decision cost and what it changed, keep the method available but not central, and be plain about the limits of your evidence. Overstating a result is the fastest way to lose this round.
What to demonstrate
- Whether you can explain a technical choice to someone who will never read your code, without either flattening it into nothing or hiding inside jargon
- Honesty about evidence strength: what the analysis establishes, what it only suggests, and what it cannot say at all
- How you take disagreement, specifically whether you update on a good objection, hold your position with reasons, or fold on contact
How to prepare
- Write the two-sentence version of your most technical project for a non-specialist, then check that neither sentence needs a method name to make sense.
- For one result you are proud of, write the strongest objection someone could raise and a response that concedes the part of it that is correct.
- Prepare one decision that turned out to be wrong: how you found out, what it cost, and what you changed afterwards. A senior cross-functional interviewer asks for this more often than a technical one does.
Where candidates lose points
Slicing a flat experiment until a segment reaches significance
Testing one metric across twenty segments at a nominal 5% level produces a significant result about two thirds of the time when nothing is happening anywhere, and the segment that surfaces is by construction the one with the most favourable noise. The reported effect in that slice is then badly overstated, because selection on significance conditions the estimate on being large. What makes it dangerous rather than merely wrong is that a post-hoc segment always has a plausible story attached, so it survives the meeting. The controls are declaring the small number of segments of interest before launch, correcting across the ones tested, and treating anything discovered afterwards as a hypothesis that needs its own adequately-powered test rather than a finding.
Treating last-touch attribution as the causal value of a channel
The attribution label on dim_user is the output of a rule that assigns full credit to whichever touch happened to be recorded last inside a lookback window, and that rule systematically rewards channels that sit close to the conversion, especially branded search and retargeting, which largely intercept demand that already existed. Reallocating spend on those labels moves budget toward the channels that are best at being last, which is why attributed return on ad spend often improves while total signups do not. Nothing in the touchpoint data can settle this, because the counterfactual of not running the channel was never observed. The credible reads are a geo holdout or a scheduled pause, sized in advance on the total-signups metric rather than on the attributed one, and the honest framing in the meantime is that the label describes correlation with conversion and not incremental contribution.
SQL that silently fans out on a one-to-many join
State the grain of each table and the grain you want in the result before writing the join. Pre-aggregate the many side to the join key, or use EXISTS or a window function, and verify with a row count against COUNT(DISTINCT id) rather than trusting that the numbers look plausible.
Sizing estimates built on unnamed, unrevisable assumptions
Write each assumption as a named number you can change, then show the arithmetic so the interviewer can challenge one input instead of the whole answer. Finish by saying which assumption the result is most sensitive to, which matters more than the point estimate.
Questions & practice
What motivates you to pursue a career in data science within the food distribution industry?
Medium · statistics and probability
Approach
- Sanity-check the answer against a simple bound or a simulated case.
- Say what the estimate is of, and over what population it generalises.
- Write down the assumption the method needs before you use the method.
Follow-up
- How would you explain this result to someone who does not know statistics?
- What sample size would you need to detect an effect half this size?
How would you approach a situation where your model’s performance begins to degrade in production?
Medium · machine learning and modelling
Approach
- Say how the offline result would be validated online before it is trusted.
- Frame the prediction: the label, the moment of prediction, and the action it triggers.
- Set a baseline first, so any model has something honest to beat.
Follow-up
- What would you monitor after launch to know the model is still valid?
- How would you choose the decision threshold, and who owns that choice?
How do you select the appropriate machine learning algorithm for a specific business problem?
Medium · machine learning and modelling
Approach
- Set a baseline first, so any model has something honest to beat.
- 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
- Where could label leakage enter this setup?
- How would you choose the decision threshold, and who owns that choice?
Rebuild per-visitor ordering without groupby convenience methods
Easy · pandas · vectorisation · window logic
You have a DataFrame of 2 million fct_event rows with visitor_id, occurred_at_utc and event_id, unsorted and containing duplicate timestamps within a visitor. Produce three new columns: event_rank, the 1-based position of the event within its visitor ordered by occurred_at_utc; seconds_since_prev, the gap to that visitor's previous event, NULL for the first; and is_first_for_visitor. You may use sort_values, shift, cumsum, numpy and boolean masking. You may not use groupby.transform, groupby.apply, groupby.cumcount, groupby.rank or merge_asof. Break timestamp ties on event_id.
Approach
- Sort once by ['visitor_id', 'occurred_at_utc', 'event_id'] and reset the index. The whole exercise reduces to row arithmetic on a sorted frame, and the tiebreak on event_id is what makes the result reproducible across runs.
- Mark visitor boundaries with is_first = df['visitor_id'].ne(df['visitor_id'].shift()). This is the single fact every other column derives from.
- Compute seconds_since_prev as the diff of the timestamp column, then overwrite it with NaT/NaN wherever is_first is True. The shift crosses the boundary between visitors and will otherwise hand the first row of each visitor the last event of the previous one.
- Build event_rank from a running counter that resets at boundaries: take a global cumulative position (np.arange(len(df))) and subtract, per row, the global position at which that visitor started. Get the start position by forward-filling the positions where is_first is True, which is a cumsum-free reset and is O(n).
- Verify against the forbidden method once, as a test rather than as the implementation, and confirm the two agree on every row.
Worked solution (20 min)
- Sort on the three-key tuple and reset_index(drop=True).
- Compute is_first via .ne(.shift()), which is True for row 0 because the shifted value is NaN.
- pos = np.arange(len(df)); start = pd.Series(np.where(is_first, pos, np.nan)).ffill(); event_rank = (pos - start + 1).astype(int).
- gap = df['occurred_at_utc'].diff().dt.total_seconds(); gap[is_first] = np.nan.
- Assert event_rank equals df.groupby('visitor_id').cumcount() + 1 on the sorted frame.
Expected result: Three columns on the sorted frame: event_rank starting at 1 for every visitor and increasing by 1 with no gaps, seconds_since_prev null exactly where is_first_for_visitor is True, and is_first_for_visitor summing to df['visitor_id'].nunique().
Verify it yourself
- is_first_for_visitor.sum() equals the number of distinct visitor_id values.
- seconds_since_prev.isna().sum() equals that same number.
- event_rank.max() per visitor equals that visitor's row count, and event_rank is non-negative everywhere.
- A visitor with two events at the identical timestamp gets ranks 1 and 2 with a gap of 0.0, ordered by event_id.
Follow-up
- The frame does not fit in memory. How does your approach change if you can only process one visitor-partitioned chunk at a time?
- occurred_at_utc is client-supplied and sometimes runs backwards within a visitor. Does your seconds_since_prev go negative, and should it?
- How would you extend this to reset the counter at every change of surface as well as visitor?
Weekly visit-to-signup conversion split by acquisition channel
Easy · funnel · conversion · aggregation
From fct_session (session_id, visitor_id, started_at_utc, referrer_channel, is_bot_flagged, consent_state) and fct_event (visitor_id, occurred_at_utc, event_name), compute visit-to-signup conversion for one ISO week, split by channel. session_id is the unique key of fct_session. Denominator: distinct visitor_id with a session starting in the week, is_bot_flagged = FALSE and consent_state <> 'denied'. Numerator: those visitors with a 'signup_completed' event in the same week. Label each visitor with the referrer_channel of their first session in the window. Return channel, visitors, signups and rate, plus one all-channel total row.
Approach
- Build a visitor spine that is one row per visitor: filter sessions to the week, drop is_bot_flagged and consent_state = 'denied', then take the first session per visitor with ROW_NUMBER() OVER (PARTITION BY visitor_id ORDER BY started_at_utc, session_id) = 1 to carry the channel label. Collapsing to one row here is what makes the channel buckets mutually exclusive and the totals additive.
- session_id is the unique key, so that ordering is total and the label is reproducible. If the table carried no unique key you would have to write an explicit tie rule instead, because two sessions on different channels at the identical timestamp would otherwise label the visitor differently between runs.
- Attach the outcome as a semi-join (EXISTS on a signup_completed event for that visitor inside the same week) rather than a join to the event table, so a visitor who fires the event twice does not count twice and inflate the numerator past the denominator.
- Aggregate with COUNT() as visitors and COUNT() FILTER (WHERE signed_up) as signups, and compute the rate as signups::numeric / NULLIF(visitors, 0) so an empty channel returns NULL rather than a division error.
- Produce the total with GROUP BY GROUPING SETS ((channel), ()), which re-sums numerator and denominator for the total row. Averaging the channel rates gives a different and wrong number whenever channel volumes differ, which they always do.
- Verify the spine before trusting the output: COUNT(*) must equal COUNT(DISTINCT visitor_id), and the per-channel visitor counts must sum to the total row.
Worked solution (20 min)
- Write the filtered session CTE and check its row count against an unfiltered count, so you know how much volume the bot and consent filters removed.
- Add the ROW_NUMBER first-session pick and assert one row per visitor.
- Add the EXISTS outcome flag and aggregate with FILTER.
- Add GROUPING SETS for the total and format the rate to four decimal places.
- Spot-check one channel by hand: pull its visitor list, count signups directly, compare.
Expected result: One row per referrer_channel plus a total row, with visitors, signups and a decimal rate. Channels with zero signups appear with rate 0, not missing. The total rate lies strictly between the minimum and maximum channel rate unless one channel holds all the volume.
Verify it yourself
- Per-channel visitor counts sum exactly to the total row's visitor count.
- COUNT(*) equals COUNT(DISTINCT visitor_id) on the spine CTE.
- Replacing the EXISTS with a plain inner join changes the signup count only if duplicate signup_completed events exist; if it does change, say by how much.
- Every rate is between 0 and 1.
- Re-running the first-session pick with session_id removed from the ORDER BY returns the same labels; if it does not, visitors hold same-timestamp sessions on different channels and the tie rule is doing real work.
Follow-up
- The denominator is distinct visitors. If a browser release shortens cookie lifetime, what happens to this rate, and how would you tell that apart from a genuine drop?
- A visitor's first session is direct and their signup session is paid search. Your label says direct. When is that the wrong answer for the decision being made?
- How do you roll four weeks into a month, and why is averaging the four weekly rates wrong?
Seven-day activation rate by weekly signup cohort
Easy · cohorts · activation · date-bounds
dim_user holds user_id, account_created_at_utc, is_internal. fct_event holds user_id, occurred_at_utc, is_core_action. A user is activated when core-action events fall on at least two distinct UTC dates inside [account_created_at_utc, account_created_at_utc + 7 days). Return, for the last twelve complete weekly signup cohorts, the cohort week, cohort size, activated users and the activation rate. Exclude is_internal users. Every signup in the cohort week stays in the denominator, including users who never returned.
Approach
- Start from dim_user as the denominator spine with is_internal = FALSE and DATE_TRUNC('week', account_created_at_utc) as the cohort key. Driving the query from the event table instead would silently condition on having events and delete the entire non-activating population.
- Join fct_event on user_id with is_core_action = TRUE and a per-user bound, occurred_at_utc >= u.account_created_at_utc AND occurred_at_utc < u.account_created_at_utc + interval '7 days'. The bound is correlated to each user's own signup timestamp, not a single global date range.
- Aggregate per user with COUNT(DISTINCT occurred_at_utc::date) >= 2, then LEFT JOIN that back onto the spine and COALESCE the flag to FALSE so non-activators contribute a zero rather than vanishing.
- Restrict the published cohorts to those whose week ended at least eight days ago. A cohort younger than that has not finished its seven-day window, so its rate is mechanically low and reads as a decline.
- Roll up by summing the numerator and denominator per cohort week, and state the two-distinct-days threshold next to the number since it is a choice that re-bases the whole history if changed.
Follow-up
- Why two distinct days rather than one event? What happens to the published history if someone changes it to three?
- Invited seats and SSO-provisioned users get an account_created_at_utc at provisioning and may never sign in. Should they be in this denominator?
- The rate rose 3 points this week. What do you check before believing it?
What metrics would you prioritize when evaluating the success of a demand forecasting model?
Medium · product sense and metrics
Approach
- Fix the population and the time window before naming any metric.
- Restate the decision this analysis has to support, and who acts on the answer.
- State what result would change your recommendation, so the answer is falsifiable.
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?
A surrogate for twelve-month value inside a three-week test
Hard · surrogate metrics · experimentation · long-term value
A monetisation change — an earlier and harder paywall — will be tested for three weeks. The decision quantity is twelve-month cumulative net revenue per acquired account, which cannot be observed in three weeks. Available: fct_event, fct_session, fct_subscription_period (mrr_cents_constant_fx, period_status, change_reason, is_first_paid_period), dim_account, and fct_experiment_exposure (unit_type, unit_id, variant, first_exposed_at_utc, is_in_analysis_population). Construct a surrogate index readable at three weeks, state the assumption that makes it valid, name the mechanism that breaks it, and give the pre-registered rule for refusing to decide. Deliverable: the index, the assumption, and the refusal rule.
Approach
- State the surrogacy condition before building anything: a surrogate is valid only if the treatment's entire effect on twelve-month revenue runs through it. Then say where it fails here, because a paywall moves short-run revenue directly and long-run revenue through churn and through who becomes a payer at all.
- Fit the index on history rather than on intuition: regress twelve-month cumulative net revenue per account on features observable by day 21 — first-paid flag, activation days, week-3 core-action days, seats billed — using cohorts old enough to have a twelve-month outcome, and hold out a later cohort.
- Report the out-of-sample fit as the headline rather than the point estimate: the held-out R-squared and the calibration of predicted against actual deciles are what license any use of the index, and a decile plot catches the mis-calibration an R-squared hides.
- Name the bias direction explicitly: the relationship was estimated under the old paywall, so under a harder paywall the marginal payer is a different person and the index over-predicts their value, biasing the treatment arm optimistic.
- Surround the index with the direct three-week readouts it cannot contain — revenue per exposed unit, cancel-within-first-period rate, and the free-side signup and activation counts — and pre-register a numeric refusal rule, because the failure mode of a surrogate is being used confidently in exactly the case it was not fit for.
Worked solution (40 min)
- Assemble the training set: accounts with first_paid_at_utc at least twelve months old, their realised twelve-month cumulative net revenue, and the day-21 features.
- Fit on pre-change cohorts, evaluate on a held-out later cohort, and report both out-of-sample R-squared and predicted-versus-actual decile calibration.
- Write the surrogacy assumption as a falsifiable sentence, then write the specific mechanism in this treatment that violates it.
- Specify the direct arm readouts on fct_experiment_exposure with is_in_analysis_population = TRUE: revenue per exposed unit, cancel-within-first-period rate, and free-side signup and activation counts.
- Write the refusal rule with numbers attached, covering both the fit threshold and the free-side divergence case.
Expected result: A fitted surrogate with a reported out-of-sample R-squared and calibration, a written surrogacy assumption plus the mechanism that violates it, the resulting bias direction, and a numeric refusal rule agreed before launch.
Verify it yourself
- Standard errors are clustered on fct_experiment_exposure.unit_id, the identifier of the randomisation unit. unit_type is the kind label for that unit — a single value such as 'user' across the whole table — so clustering on it collapses the design to one or two clusters and carries no information about who was randomised together. A per-event or per-session standard error on a user-randomised test is the opposite error: too small, and it will manufacture significance.
- The analysis population is the exposed set with is_in_analysis_population = TRUE, and a sample-ratio check against the intended split passes before any outcome is read.
- The index applied to the control arm should reproduce the historical relationship. If control is already mis-calibrated, the treatment estimate is not interpretable at all.
Follow-up
- What evidence would make you trust a surrogate for this particular change rather than a different one?
- The index reads plus 8 percent while three-week cancel-within-period is up. Which do you act on, and what do you tell the decision-maker?
- How would you size this test, on what unit, and what does that do to the three-week horizon?
Pooled signup conversion fell while every segment rose
Medium · mix-shift · simpsons-paradox · decomposition
Weekly visit-to-signup conversion, counted on distinct fct_session.visitor_id with is_bot_flagged = TRUE and consent_state = 'denied' sessions excluded, fell from 4.4% to 3.9% week over week. Split by device_type and referrer_channel, all twelve cells are flat or up. A paid_social campaign launched on Monday. Using fct_session and fct_event, quantify how much of the 0.5-point fall is mix and how much is within-segment rate, then state what you would tell the growth lead.
Approach
- Write the pooled rate explicitly as the sum over segments of weight times segment rate, and materialise both weeks' weights and rates into one table. Until that table exists there is nothing to decompose, only opinions.
- Compute three quantities and report all three: the rate effect holding the prior week's weights fixed, the mix effect holding the prior week's rates fixed, and the interaction residual. Reporting only the first two hides a term that can be material when both weights and rates move a lot.
- Rank segments by their individual mix contribution, computed as the change in that segment's weight multiplied by its prior-period rate. This is what lets you say one cell caused the move rather than gesturing at the campaign.
- Verify the new traffic is human and countable before accepting the mix story: check is_bot_flagged coverage on the new channel, the distribution of duration_seconds and event_count for its sessions, and whether its consent_state profile differs from the rest.
- Deliver the conclusion as a definition change rather than a diagnosis: a pooled rate over a mix that moves is not comparable week over week, so the recurring report should carry per-channel rates plus absolute signups, with the pooled figure demoted or dropped.
Follow-up
- Paid social converts at roughly a quarter of organic but absolute signups rose. Is the campaign working, and what would you need to answer that properly?
- Would you reach the same conclusion if the campaign had moved the mix by two points instead of sixteen? Where is your threshold and why?
- How would you present this to someone who has been watching the pooled number in a weekly meeting for a year?
Suggested practice checklist
For a candidate whose interviews will centre on A/B testing, metric movement and causal claims. Design comes before arithmetic, arithmetic before analysis, and the week ends by rehearsing the readout rather than the derivation.
Day 1 — Design one test end to end on paper
- Take a single feature change and write the full design: randomization unit, the exact point of exposure, the primary metric with its grain, guardrails, allocation, planned duration, and the decision rule committed before any data exists.
- Write why the randomization unit must sit at or above the level where treatment can spill over, and give one case where user-level randomization is still contaminated (shared accounts or devices, or two participants in the same marketplace).
- State in advance what you will do if the primary metric is flat while a secondary metric is significant.
Deliverable: A one-page test design with a decision rule written before launch.
Day 2 — Power arithmetic until it is automatic
- Compute required sample size per arm for a binary metric with the normal approximation, n is approximately 2 times (z for alpha/2 plus z for power) squared times p(1 minus p) divided by delta squared, for baselines of 2, 10 and 40 percent at a 5 percent relative lift, and note that for a fixed relative lift the requirement falls as the baseline rises because delta grows proportionally with p.
- Redo the calculation for a continuous metric using variance in place of p(1 minus p), and show why a heavy-tailed quantity such as revenue per user needs either far more traffic or a capped version with a stated cap.
- Convert one of the results into weeks given a weekly eligible traffic figure, then list the two honest ways to shorten it (accept a larger detectable effect, or reduce variance) and write why quietly lowering the power target is a decision to miss more real wins, not a speedup.
Deliverable: A small script or sheet that maps baseline, minimum detectable effect, alpha and power to sample size and weeks, cross-checked against a published calculator.
Day 3 — Variance and the unit-of-analysis problem
- Take a ratio metric whose denominator is not the randomization unit (clicks per session, randomized by user) and compute the standard error twice, once naively at session level and once by the delta method or a user-level bootstrap, then record how much the naive version understates it.
- Implement CUPED on simulated data: choose a pre-period covariate X measured before assignment, estimate theta as Cov(Y, X) divided by Var(X), and analyse Y minus theta times (X minus its mean) in place of Y. Confirm the variance of the adjusted outcome equals the raw variance multiplied by one minus the squared correlation between Y and X, so a correlation of 0.45 removes about 20 percent of the variance and not 80.
- Now run that simulation a few hundred times and confirm the adjusted effect estimate is unbiased for the same effect rather than numerically identical to the raw one. Within any single run the two differ, sometimes by a large fraction of the true effect, because the two arms' pre-period covariate means never coincide exactly in a finite sample; they agree in expectation, which is the property that matters and the one to state out loud.
Deliverable: A notebook showing the adjusted estimator with a measurably smaller variance than the raw one, plus a repeated-simulation table showing the two estimators agreeing on average while differing run by run.
Day 4 — Validity threats you can actually test for
- Run a sample ratio mismatch check as a chi-square goodness-of-fit test against the intended allocation, and write the three causes you would chase first (assignment logged before exposure, an arm-specific redirect or load failure, bot filtering applied asymmetrically).
- Simulate peeking: generate A/A data, test daily at alpha 0.05 across 14 looks, record the inflated false positive rate, then apply an alpha-spending boundary or commit to a fixed horizon and confirm the rate returns to nominal.
- Write how you would separate a novelty effect from a durable lift using the treatment effect plotted against days since first exposure, and what shape would change your recommendation.
Deliverable: One table showing the peeking false positive rate before and after correction, plus a written SRM triage list.
Day 5 — When randomization is not available
- Write the identifying assumption for difference-in-differences (parallel trends in the absence of treatment), then plot pre-period trends for two candidate control groups and justify rejecting one of them.
- Design a switchback test for a change where user-level randomization would leak across participants, choosing a time-block length against the carryover you expect and saying how you would detect carryover in the data.
- List what an interrupted time series or a synthetic control buys you and the one thing neither can rule out: an unobserved shock that coincides with the launch.
Deliverable: A one-page memo recommending a single quasi-experimental design and naming its weakest assumption explicitly.
Day 6 — The readout query
- Write the assignment-to-exposure join that returns exactly one row per unit per experiment, and handle units appearing in both arms by excluding and counting them rather than silently keeping one.
- Compute the per-arm metric, its variance and the relative lift with a confidence interval in SQL, then reproduce the identical numbers in a notebook as a cross-check.
- Add a segment breakdown and write the sentence that keeps it from being p-hacking: segments declared in advance, everything else reported as exploratory and corrected for multiplicity.
Deliverable: A single query that outputs the full readout table, matched to a notebook recomputation.
Day 7 — Present it to someone who will not read the appendix
- Give a 10-minute readout of a real or simulated experiment in the order decision, number, uncertainty, caveat.
- Have your listener ask "can we ship it" in the case where the primary is flat and a guardrail moved, and answer with a recommendation rather than a request for more data.
- Rewrite your opening line so the recommendation lands before any methodology.
Deliverable: A one-page readout whose first line is the recommendation.
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.
Can you describe a time you had to clean or handle a messy, incomplete dataset to reach a conclusion?
Medium · behavioural and stakeholder questions
Approach
- Name the disagreement or constraint, and how you resolved it with evidence.
- Close with what you would do differently, concretely.
- Pick a story where you drove the decision, not one where you observed it.
Follow-up
- What would you do differently if you ran that project again?
- What did you decide not to do, and why?
Tell me about a time you had to explain a complex technical concept to a non-technical audience.
Medium · behavioural and stakeholder questions
Approach
- Pick a story where you drove the decision, not one where you observed it.
- Quantify the outcome, including what you would not claim credit for.
- 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?
Quantify your own impact without claiming the topline you touched
Hard · self-assessment · attribution · communication
You are writing the impact section of your own review. Over the year you ran four experiments, one of which shipped and three of which were flat; you corrected the definition of gross monthly revenue churn so that cancellation is recognised at period_end_utc; and you built a self-serve funnel dashboard. Weekly active accounts rose 14% over the same period. Your reviewer knows the data well. Write the three impact claims you would defend, stating for each what you contributed, what evidence supports it, and what portion of the outcome you are not claiming.
Approach
- Recognise what is being probed: whether you apply to your own work the causal standard you would apply to somebody else's roadmap claim. Nearly everyone who would reject 'accounts that do Y retain better' will write 'I drove a 14% increase' without noticing it is the same error with a friendlier subject.
- Sort the work by the kind of evidence it can carry. The shipped experiment is the only item with a randomised estimate, so it is the only one where an effect size is defensible, and you claim the interval rather than the point estimate.
- Claim the three flat experiments as decisions prevented and price them. Features not built, or built differently, on evidence, with the engineering weeks reallocated as the number somebody else can verify. A defensible null is a delivered decision and should be written as one.
- Claim the definition fix as correctness, not as improvement. The old figure was overstated by a specific percentage and appeared in a specific set of recurring documents; the impact is the change it produced in the forecast built on top of it, not a change in churn itself.
- Claim the dashboard on usage and displacement: distinct weekly users of it, and the ad-hoc request count for six months before against six months after. If the request log does not exist, record the claim as unverified rather than estimating it upward.
- Disclaim the 14% explicitly and once. State that it cannot be separated from seasonality, other teams' launches and a pricing change, and bound your own contribution from above using the shipped experiment's interval converted into headline units.
Follow-up
- Your shipped experiment's interval was +0.2pp to +1.4pp on activation. How much of the 14% can that account for, and how do you say so without undercutting yourself?
- A peer in the same cycle claims the full 14%. What, if anything, do you do about it?
- If you could only keep two of your three claims, which do you drop, and why that one?