Interview guides · MediaMath
MediaMath Data Scientist Interview Questions & Guide 2026
Practice explaining analysis before a MediaMath data scientist interview: the recommendation, the uncertainty, the follow-ups.
The 60-second brief
A Data Scientist at MediaMath plays a pivotal role in harnessing data to drive strategic decisions and optimize digital advertising solutions. This position is crucial for developing algorithms and analytical models that enhance the efficiency and effectiveness of marketing campaigns. As a Data Scientist, you will work closely with cross-functional teams, leveraging large datasets to uncover insights that impact product features and user experiences.
Seniority shifts the scope more than the words in the title do. Earlier-career loops mostly check that you execute a well-posed analysis correctly; senior loops check that you can decide which question is worth answering and defend what you chose not to do.
Focus areas
- Separate release-calendar spikes from underlying trend
- Correct discovery slates for position bias
- Define a qualified stream and defend it
Overview
In this role, you will tackle complex challenges, often involving vast amounts of data from diverse sources. Your contributions will help shape how advertisers target audiences and measure campaign success, influencing both company performance and user satisfaction. Expect to engage with advanced analytical tools and methodologies, making your work not only impactful but also intellectually stimulating. By joining MediaMath, you will be at the forefront of innovation in the advertising technology industry.
The MediaMath interview process
MediaMath candidates report 3 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.
Technical Interviews
Much of what gets scored here happens out loud while you type. Nobody can see your reasoning inside a half-written query, so five silent minutes read as being stuck even when they are not. State the plan in plain language first: which tables, what grain you are aggregating to, and the one filter that defines the population. Then write it. The narration doubles as insurance, because a wrong plan gets caught early and cheaply while a wrong query gets caught at the end with no time left to redo it. A timed statistics section, where one exists, is a separate test with its own clock.
What to demonstrate
- Whether the query you write matches the plan you just described
- What you do with a hint, meaning whether the correction gets absorbed or the first approach gets defended
- Whether you can debug your own wrong output by reading the result set and naming which part of the query produced the anomaly
How to prepare
- Solve three problems while screen-sharing into a recording, then watch it back and mark every stretch longer than thirty seconds where you said nothing
- Practise compressing the plan into one sentence before typing, then check afterwards whether the finished query actually matched it
- Time yourself on statistics questions that carry a business reading, such as what a confidence interval does and does not claim, rather than re-reading notes without a clock
Behavioral Assessments
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.
Case Study or Coding Challenge
A case has a fixed clock, and a good deal of what is being scored is how you spend it. Thirty to forty-five minutes buys one pass across the whole problem or a deep read of one part of it, and choosing between those is the work rather than a compromise forced on you. Announce the shape early: the structure you are using, the branch you think carries the decision, and what you are setting aside. An answer that is thorough for the first third and silent on the recommendation reads worse than one that is rougher throughout and lands.
What to demonstrate
- Whether a visible structure appears in the opening minutes and survives the rest of the case
- Whether the depth goes to the branch that carries the decision, rather than the branch you find most comfortable
- Whether you say what you are leaving out and why, instead of quietly omitting it and hoping nobody asks
How to prepare
- After each practice case, write down the branches you chose not to open and the reason for each, then check whether you said any of them out loud while the case was running. A branch you only cut privately reads to the interviewer as one you missed.
- Redo a case you have already worked in half the time, deciding in advance which single branch you keep, then compare which version a listener would find more useful.
- Write a two-sentence opening you can reuse, holding the restated question and your plan for the available time, and deliver it within the first ninety seconds of every practice run.
Where candidates lose points
Collapsing cancellation and payment failure into one churn number.
Involuntary churn from expired or declined payment instruments is a large and volatile share of gross churn, and it responds to retry schedules, card-updater coverage and billing provider, not to anything in the product. It also resolves late, so a period that looks involuntary today can be a successful retry next week, and reading the split before the dunning horizon closes overstates it. Compounding this, cancel-at-period-end means the cancellation request and the entitlement end are different timestamps on different rows, so a churn curve keyed on cancel_requested_ts and one keyed on churn_ts disagree by a full billing period.
Counting plays without a qualification threshold, or changing the threshold without restating history.
Playback arrives as heartbeats, so a play only exists once you decide what counts, and the common 30-second convention is not a neutral analytics choice: in music it is also the boundary at which a play becomes payable, which makes the warehouse definition a payout definition. The threshold interacts violently with content length, so a catalogue of three-minute tracks and one of forty-minute episodes move in opposite directions when you change it, and a skip-heavy surface can add plays while adding no hours. Any metric mixing pre-threshold and post-threshold counts, or pooling short-form and long-form on a per-stream basis, moves by double digits for reasons that have nothing to do with the product.
Reaching for a model before the target metric exists
Before naming an algorithm, write down the label, the prediction time, and the action that changes when the score crosses a threshold. If you cannot say what decision the output drives, any modelling choice is guesswork dressed up as method.
Building features from data that postdates the prediction time
Check every feature against the timestamp at which the model would actually score, and drop anything computed from a window that includes or follows the label event. For a forecasting use case, split train and test by time rather than at random, and split by entity when the same entity recurs.
Questions & practice
Explain the bias-variance tradeoff.
Medium · statistics and probability
Approach
- Translate the result into the decision it informs, in one plain sentence.
- Write down the assumption the method needs before you use the method.
- Sanity-check the answer against a simple bound or a simulated case.
Follow-up
- How would you explain this result to someone who does not know statistics?
- Which assumption here is most likely to be violated in practice?
Write code to perform linear regression on a given dataset.
Medium · machine learning and modelling
Approach
- Say how the offline result would be validated online before it is trusted.
- Pick an evaluation metric that matches the cost of each error type, not a default.
- Check what information would not exist at prediction time, and exclude it.
Follow-up
- Where could label leakage enter this setup?
- What would you monitor after launch to know the model is still valid?
Duration-decile-weighted completion rate with fixed reference weights
Hard · metric-definition · standardisation · pandas
Implement this metric. Numerator: qualified streams with completion_ratio at or above 0.9. Denominator: qualified streams with a non-null duration_seconds, so live events are out. Compute the rate inside each (content_type, duration decile) cell, then aggregate with catalogue-mix weights fixed from a reference month. You get streams and content for the last eight weeks plus ref_streams for the reference month. Return the weighted index and the unweighted global rate for each of the eight weeks, and the share of reference weight your cells actually covered.
Approach
- Fix the decile boundaries from the reference month, within content_type, over that month's qualified streams. Not over the catalogue, and not per week: the weights and the cells have to be defined on the same population or the weighted sum is adding rates over cells the weights do not describe.
- Store the boundaries explicitly and bin every week against them with pd.cut, with open-ended outer edges, so a duration longer than anything in the reference month still lands in the top cell instead of becoming NaN and quietly leaving the denominator.
- Weights are the reference month's share of qualified streams per (content_type, decile) cell, summing to one across all cells. Apply them to each week's cell rates and report the covered weight separately, because a week missing a cell entirely gives a renormalised index, and renormalising silently is how the series gains a step change nobody can explain.
- Keep the unweighted rate beside it. The pair is the deliverable: the weighted line is the answer, and the gap between the two is the size of the mix effect you removed, which is the first thing anyone reading it will ask about.
- Sanity-test the whole construction by feeding the reference month back in as the current week; the weighted and unweighted rates must then be identical to floating-point error.
Worked solution (40 min)
- Join content onto ref_streams, filter to is_qualified with duration_seconds not null, and take within-content_type deciles of duration_seconds at quantiles 0.1 through 0.9, replacing the outer edges with negative and positive infinity.
- Weights: value counts of (content_type, decile) over the reference month, divided by that month's total qualified, non-null-duration streams.
- For each of the eight weeks, filter and join identically, bin with pd.cut against the stored per-type boundaries, and compute each cell rate as the mean of (completion_ratio at or above 0.9), keeping the cell's stream count alongside.
- Weighted index = sum(weight times rate) over cells present that week, divided by the sum of weight over those same cells; record that divisor as covered_weight.
- Unweighted rate = the week's overall mean of the same indicator. Assemble the eight-row output.
Expected result: An eight-row frame of week_start, weighted_rate, unweighted_rate and covered_weight, where covered_weight is at most 1.0 and the two rates coincide only in a week whose cell mix matches the reference month's.
Verify it yourself
- Feeding ref_streams in as the current week returns weighted_rate equal to unweighted_rate to floating-point error, with covered_weight exactly 1.0.
- Duplicating every stream in one cell changes the unweighted rate and leaves the weighted rate unchanged. This is the point of the metric and the fastest proof the weighting is wired up.
- Each reference decile holds about ten percent of that content_type's reference streams. A cell holding thirty percent means ties in duration_seconds collapsed the quantile edges, which is common where many items share a rounded runtime.
- No row with content_type of live_event, or with a null duration_seconds, survives into either the reference frame or any weekly frame.
Follow-up
- The weighted index is flat and the unweighted rate fell four points. What shipped?
- When would you refresh the reference month, and what do you owe the series when you do?
- Podcast episodes and film have very different completion shapes. Would you ever report one number across them at all?
Describe how you would handle outliers in a dataset programmatically.
Medium · sql and data manipulation
Approach
- Check whether any join is one-to-many before aggregating, or the sums inflate.
- State the window function and its partition and ordering out loud before writing it.
- Say which table is the grain you start from, and join outward from it.
Follow-up
- How would you verify this result without re-running the same query?
- What breaks if events arrive late or out of order?
Can you explain how you would optimize a SQL query for speed?
Medium · sql and data manipulation
Approach
- Check whether any join is one-to-many before aggregating, or the sums inflate.
- Handle the rows that do not match: a LEFT JOIN with a NULL check is usually the question.
- Say which table is the grain you start from, and join outward from it.
Follow-up
- How would you verify this result without re-running the same query?
- How does the query change if the join becomes one-to-many?
Rebuffer ratio by point of presence, excluding start failures
Easy · ratio metrics · delivery quality · conditional aggregation
fct_stream carries cdn_pop, device_type, network_type, played_seconds, rebuffer_seconds and end_reason (enum including 'playback_error'). For streams with started_at in the trailing 24 hours, return one row per (cdn_pop, device_type, network_type) with two figures: rebuffer_ratio = SUM(rebuffer_seconds) / SUM(played_seconds), computed only over rows with played_seconds > 0; and start_failure_rate = the share of all rows in the cell with played_seconds = 0 and end_reason = 'playback_error'. Restrict output to cells with at least 500 streams and order by rebuffer_ratio descending.
Approach
- Do it in one pass with conditional aggregation: SUM(rebuffer_seconds) FILTER (WHERE played_seconds > 0) over SUM(played_seconds) FILTER (WHERE played_seconds > 0), or the equivalent SUM(CASE WHEN ...) on engines without FILTER. Two separate scans risk the numerator and denominator disagreeing about which rows are in the cell.
- Wrap the denominator in NULLIF(..., 0) so a cell in which every stream failed before first frame returns NULL rather than raising a division error — and so it is visibly absent rather than silently zero.
- The failure rate's denominator is every row in the cell, including the zero-play ones. That is the whole point of the pair: the rows excluded from the ratio have to be counted somewhere, or a point of presence that fails before first frame reports a perfect ratio.
- Apply the 500-stream minimum with HAVING COUNT(*) >= 500 before ordering, otherwise a cell with three streams and one stall tops the list at a ratio no one should act on.
Worked solution (15 min)
- Write the cell aggregate exposing the raw components: COUNT(*), the two filtered sums, and the count of zero-play playback errors.
- Derive both rates in an outer SELECT so the raw counts stay visible when a cell looks wrong.
- Recompute the ratio ungrouped over the same filtered rows and confirm it equals the played_seconds-weighted mean of the cell ratios.
Expected result: One row per (cdn_pop, device_type, network_type) cell with at least 500 streams, carrying stream_count, rebuffer_ratio >= 0 (NULL where no row had played_seconds > 0) and start_failure_rate in [0, 1].
Verify it yourself
- The ungrouped ratio equals the played_seconds-weighted mean of the per-cell ratios, not their unweighted mean.
- Every cell whose start_failure_rate is exactly 1.0 has a NULL rebuffer_ratio.
- COUNT(*) summed across all cells before the HAVING equals the unfiltered row count for the 24-hour window.
Follow-up
- Two points of presence show the same rebuffer ratio but one has twice the start-failure rate. Which do you escalate, and what does the second number tell you that the first cannot?
- How would you decide whether a cell's ratio moved beyond what day-to-day variation explains, given that cell sizes differ by orders of magnitude?
Given a dataset, how would you approach identifying trends?
Medium · product sense and metrics
Approach
- Name one primary metric, then the guardrail that stops it being gamed.
- State what result would change your recommendation, so the answer is falsifiable.
- 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?
Explain how you would analyze user engagement data to inform product decisions.
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.
- Decompose the metric into the rates that drive it, and say which one you would check first.
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?
How do you prioritize tasks when multiple projects demand your attention?
Medium · product sense and metrics
Approach
- Fix the population and the time window before naming any metric.
- Name one primary metric, then the guardrail that stops it being gamed.
- Restate the decision this analysis has to support, and who acts on the answer.
Follow-up
- 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 you had to choose between two models with similar performance, how would you decide which to implement?
Medium · product sense and metrics
Approach
- State what result would change your recommendation, so the answer is falsifiable.
- Name one primary metric, then the guardrail that stops it being gamed.
- Restate the decision this analysis has to support, and who acts on the answer.
Follow-up
- 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?
How would you design an A/B test for a new advertising feature?
Medium · a/b testing and experimentation
Approach
- Name the guardrails that would stop a launch even on a positive primary result.
- Say whether units interfere with each other, and switch design if they do.
- Decide the analysis before seeing data, including how long it runs and when you look.
Follow-up
- What would you do if you could not randomise at all?
- How would you handle interference between treated and control units?
How would you handle missing data in a dataset?
Medium
Approach
- Work from the decision backwards to the evidence you would need.
- Clarify what is being asked and what a complete answer would contain.
- Say what you would check first and why it is the highest-information step.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
Choose a success metric for autoplay continuation
Easy · metric design · gameable metrics · guardrails · engagement
A team ships autoplay continuation: when an item ends, playback continues into an algorithmically chosen next item with no user action. Leadership proposes qualified streams per active account-week as the success metric. Using fct_stream (account_id, played_seconds, is_qualified, start_source, completion_ratio, end_reason, started_at) and dim_content_version (duration_seconds, content_type), build a metric tree for this launch, pick one primary metric and two guardrails, and give numerator, denominator and window for each. Say in one sentence how the proposed metric rises without anyone being better served.
Approach
- Show the gaming mechanism with arithmetic rather than adjectives: is_qualified is played_seconds >= 30, so a 180-second track crosses it at completion_ratio 0.167 and a queue that plays eight items unattended books eight qualified streams from one decision to press play.
- Move the primary metric off the count and onto the quantity autoplay cannot manufacture per decision: qualified hours per active account-week, numerator sum(played_seconds)/3600 over is_qualified rows, denominator distinct (account_id, ISO week) pairs with at least one qualified stream, over the four most recent complete ISO weeks.
- Set guardrail one on the mechanism itself: share of qualified streams with start_source = 'autoplay_continuation', reported as a level with a pre-registered ceiling, so the team cannot buy the primary metric by extending the queue indefinitely.
- Set guardrail two on whether the queued items were wanted: duration-normalised completion rate computed inside (content_type, duration decile) cells with catalogue-mix weights fixed from a reference month, since a raw completion rate falls whenever autoplay shifts the mix toward longer items and tells you nothing.
- Add the return test so the launch has to earn a second visit: weekly engaged accounts requires qualified streams on two or more distinct UTC dates, which one unattended evening cannot produce however long the queue runs.
- Write the ship rule before the test: hours up by a stated amount, autoplay share below its ceiling, completion rate not down beyond its non-inferiority margin.
Worked solution (20 min)
- Write both candidate primaries side by side for one hypothetical account-week: eight autoplayed 180-second tracks played to 40 seconds each gives 8 qualified streams and 0.089 qualified hours; one deliberately chosen 40-minute episode played through gives 1 qualified stream and 0.667 qualified hours.
- State which of the two account-weeks the product wants more of, and note that the count metric ranks them the wrong way round by 8 to 1 while the hours metric ranks them correctly by roughly 7.5 to 1.
- Write the primary metric out fully: numerator sum(played_seconds)/3600 where is_qualified, denominator distinct (account_id, ISO week) with at least one qualified stream, window four complete ISO weeks, exclusions is_test_account = false.
- Write the two guardrails with the same rigour, including the (content_type, duration decile) cells and the fixed reference-month weights for the completion guardrail.
- State the ship rule as a single sentence combining the primary threshold and both guardrail conditions, and note that the guardrails are conditions, not tiebreakers.
Expected result: Primary: qualified hours per active account-week. Guardrails: share of qualified streams with start_source = 'autoplay_continuation' against a fixed ceiling, and duration-normalised completion rate within (content_type, duration decile) with reference-month mix weights. Secondary return check: weekly engaged accounts on the two-distinct-UTC-date rule. The one-sentence answer is that autoplay adds qualified streams per decision, not consumption per decision.
Verify it yourself
- The primary metric's value is unchanged if the same total played_seconds arrives as one long stream or as ten short ones, which is exactly the invariance the count metric lacks.
- Every metric names a numerator, a denominator, a window and an exclusion rule; a metric written as a bare ratio without a window is not finished.
- The completion guardrail is computed within duration cells, so it cannot move purely because autoplay changed what length of item gets played.
- The return check needs two distinct UTC dates, so a single long unattended session cannot satisfy it.
Follow-up
- Hours rise and the share of end_reason = 'user_skip' rises with them. Is that a pass or a fail, and which metric in your tree should have caught it?
- How would you detect an account that fell asleep with playback running on a smart_speaker or smart_tv, and would you exclude those streams or keep them?
- Autoplay lifts hours but flattens the catalogue into the head. Which metric in the tree registers that, and if none does, what would you add?
Slate click rate jumped overnight with no ranker change
Medium · pipeline-failure · rate-denominator · logging
Home-row click rate — was_clicked over rendered rows in fct_impression (impression_id, slate_id, surface, slate_position, rendered_at, viewport_visible_ms, ranker_version, was_clicked) — rose from 4.1% to 6.8% on a single day and held there. ranker_version is unchanged across the step. Streams attributed to home_row in fct_stream (impression_id, start_source, started_at) did not rise. Using impression and stream data only, find the cause and state what you tell the ranking team, who have already claimed the improvement in a review deck. Deliverable: root cause plus the correctly restated series.
Approach
- Ask which side of the ratio moved. A rate that steps while the downstream count is flat is almost always a denominator event, so plot clicks and impressions as separate absolute series before touching the ratio again. Do the arithmetic first as well: with the numerator fixed, a rate going from 4.1% to 6.8% pins the denominator to 4.1/6.8 of its former size, so you know the size of the impression loss to look for before you open the first query.
- If impressions fell, locate the fall: group by surface, slate_position band and viewport_visible_ms = 0. Loss concentrated in high positions and in never-visible rows is the signature of a client or collector dropping below-the-fold renders, which removes mostly non-clicked rows and lifts the rate arithmetically with no behaviour change.
- Prove rows are missing rather than never rendered. Left-join fct_stream rows with start_source = 'algorithmic_slate' and non-null impression_id back to fct_impression and count the ones that no longer resolve. Orphaned attributions date the breakage to the hour and are hard to argue with.
- Look at impressions per slate_id, and require it to reconcile with the rate step. A twenty-item row now logging twelve is a truncation bug in the renderer or collector, and it accounts for the loss exactly if the distinct slate_id count is flat; a stable per-slate count with fewer slates is a sampling or partition drop upstream. The two have different owners and different backfill stories, and any per-slate figure that implies a larger loss than the rate step requires means a second thing is also wrong.
- Restate the series on a basis the defect does not touch — clicks per slate_id, or the rate restricted to slate_position <= 8 where logging is intact — and take the dated evidence to the ranking team before the number reaches the review, not after.
Follow-up
- Impressions in the truncated tail are missing for six days. Can an experiment that ran in that window still be read, and under exactly what assumption?
- What monitor catches this within an hour, and what does it cost in false alarms on a normal release day?
Suggested practice checklist
For someone who has spent the last year in notebooks, dashboards or modelling work and has not written raw SQL under time pressure. The first four days rebuild query fluency against a fixture you control and can verify by hand; the last three attach that fluency to the rest of the loop.
Day 1 — Build a fixture you can check answers against
- Create a local Postgres or SQLite database with four tables (users, sessions, events, orders) holding roughly 200 rows you generated yourself, so you know the contents well enough to predict every result.
- Deliberately seed the cases that break queries: a user with no sessions, a session with no events, two orders sharing a timestamp, a NULL in one join key, and one duplicated user row.
- Before writing any SQL, hand-compute five answers on paper (how many users placed at least one order, median orders per ordering user, and three others) and save them as the ground truth for the week.
Deliverable: A one-command seed script plus a text file of five hand-computed answers to grade every later query against.
Day 2 — Joins, filters and NULL semantics
- Answer "which users have no orders" three ways (LEFT JOIN with IS NULL, NOT EXISTS, NOT IN) and confirm that the NOT IN version returns zero rows once the subquery contains a NULL, because the comparison is never TRUE.
- Reproduce the LEFT JOIN that silently collapses to an inner join by putting a right-table predicate in WHERE, then fix it by moving the predicate into the ON clause, and record both row counts.
- Create a fan-out bug on purpose by joining orders to order_items and summing the order total, then correct it with a pre-aggregated subquery and explain in one line which table changed the grain.
Deliverable: One annotated .sql file holding the three join traps, each with the wrong result and the corrected result side by side.
Day 3 — Window functions and frames
- Write three window queries against the fixture: a running order total per user, the rank of each order within its user by value, and the day gap to that user's previous order, then check each against the day-one ground truth.
- Run ROW_NUMBER, RANK and DENSE_RANK over a column containing ties, print all three side by side, and write one sentence on when each is the correct choice.
- Switch one query from the default frame (RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, which is what you get when ORDER BY is present and no frame is written) to ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, and explain why the output differs only when the ORDER BY column has duplicates.
Deliverable: Three verified window queries plus a short note explaining the RANGE versus ROWS difference in your own words.
Day 4 — The four analytical query patterns
- Write a monthly retention grid: first order month per user, then months-since-first as the column, and verify that month zero equals the cohort size exactly.
- Sessionize the events table under a 30-minute inactivity rule using LAG plus a cumulative sum over a new-session flag.
- Build a four-step funnel that counts distinct users rather than events at each step, and state the rule you applied to a user who reaches step three without ever logging step two.
Deliverable: One file with the retention, sessionization and funnel patterns, each carrying a one-line note on the assumption it bakes in.
Day 5 — Write SQL the way you will have to write it live
- Set a 12-minute timer and solve three medium prompts in a plain editor with no execution and no autocomplete, then run them and tally syntax errors separately from logic errors.
- Narrate one solution aloud while writing it, stating the grain of each intermediate result (one row per user, one row per user-day) before you type its body.
- Rewrite your slowest solution as a CTE chain where every CTE name states its grain, and time yourself re-solving it from blank.
Deliverable: A recording of one narrated solution plus an error tally that separates syntax from logic.
Day 6 — One day for everything that is not SQL
- Write the preconditions of the two-sample t-test from memory, then check them: independent observations, and a difference in means whose sampling distribution is approximately normal, which at large sample sizes follows from the central limit theorem rather than from normality of the raw values.
- Write the difference between an odds ratio from logistic regression and a relative risk, and state the condition under which the two are close (low outcome prevalence).
- Prepare a 90-second answer to "how would you know this model is any good" that names the metric, the baseline you would beat, and the cost of the errors you care about.
Deliverable: One page of notes covering test preconditions, the odds-ratio caveat and the model-quality answer.
Day 7 — Full loop rehearsal
- Run a 45-minute mock with someone willing to interrupt: 20 minutes of SQL, 15 minutes defining a metric, 10 minutes on a past project.
- Re-solve from blank the two queries you were slowest on this week and compare the times against day five.
- Write a five-line answer to "walk me through a project" that puts a number in the first sentence and names the decision the work changed.
Deliverable: Mock feedback notes plus a timed project narrative you can deliver without reading it.
Ownership when the result is inconvenient
Sometimes the honest read is that the initiative did not work, and the person who commissioned the analysis was hoping otherwise. Interviewers want to know whether you softened it. Prepare the case where you delivered an unwelcome result, how you presented the uncertainty without hiding behind it, and what the team did next.
Describe a challenging project you worked on. How did you approach it?
Medium · behavioural and stakeholder questions
Approach
- Close with what you would do differently, concretely.
- Quantify the outcome, including what you would not claim credit for.
- Name the disagreement or constraint, and how you resolved it with evidence.
Follow-up
- How did you know the outcome was caused by your change?
- What would you do differently if you ran that project again?
Announce a stream-definition change that shifts payouts
Hard · cross-functional communication · metric definition · payouts
You find that the 60-second idle gap used to sessionise heartbeats into fct_stream rows splits one continuous listen into two streams whenever a phone backgrounds briefly on cellular. Correcting the gap lowers qualified stream counts on phones by an estimated four percent; total played_seconds is unchanged. Per-stream counts drive rights-holder payout shares. Deliverable: what you verify before telling anyone, the order in which you take it to the engineering owner, finance and content partnerships, and your recommendation on restating history.
Approach
- The probe is whether you can tell a technical correction from a commercial decision and keep them apart in the room. Verify the split streams are genuinely one listen before anything else: same profile_id, same content_version_id, contiguous max_position_seconds across the boundary, and a gap distribution with a spike at the background-timeout duration rather than a smooth tail.
- Compute the distributional effect, not the average. The four percent aggregate is not what anyone will argue about; recompute under the corrected gap and report which rights_holder_id groups gain and lose share, because short-form catalogue on mobile is where the splits concentrate and that is not spread evenly across counterparties.
- Defend the new rule on its own terms rather than on the direction of the number. The idle gap is a choice, so the argument is evidence that the two rows describe one continuous listen — never that the corrected count is lower and therefore more conservative, which invites the symmetric accusation next time the fix goes the other way.
- Name the ownership boundary out loud: engineering owns the sessionisation rule, finance and partnerships own whether payouts are restated. Conflating them is how a correct fix gets blocked by a commercial objection it should never have been exposed to.
- Sequence the conversations so the number stops moving before it leaves the building: engineering owner first to confirm the rule and land the fix, finance second to size the restatement, partnerships last. Recommend restating history for internal metrics so trends stay comparable, and recommend against retroactive payout adjustment unless the contracts require it — naming who must answer that contractual question rather than answering it yourself.
Follow-up
- Partnerships asks you to hold the fix until after the quarter closes. What do you do, and who else needs to know you were asked?
- How do you present a change that raises some counterparties' shares and lowers others', in the same meeting, to people who will compare notes afterwards?
- The four percent estimate itself has an interval spanning roughly two to seven percent. Does that change the recommendation or only the sequencing?
Walk through an analysis you shipped that was wrong
Medium · error ownership · data quality · postmortem
Describe a case where you delivered a result that was later shown to be wrong, and it had already been acted on. Cover how the error surfaced, whether you or someone else found it, what the wrong number caused, and what you changed afterwards. Prepare an example whose root cause was a definition, a denominator or a join — not a transcription slip. The interviewer will push on the mechanism, not the apology. Deliverable: a four-minute account that ends with a specific control now running in a pipeline.
Approach
- The probe is whether your account has a mechanism in it. Choose an error that generalises — a denominator that silently changed population, a join that fanned rows, a metric partitioned on event_date while offline playback arrived days late and landed in the wrong partition — rather than one that only teaches you to check your typing.
- State the blast radius factually and early: which decision was taken, how long the number stood, what it cost. A candidate who softens this is answering a different and easier question, and the interviewer can hear the substitution.
- Explain how it surfaced without adjusting who found it. The generalisable detail is why your own checks did not catch it, which is a statement about your checks rather than about your luck.
- Name the control you added and where it now lives: a row-count assertion after the fan-out join, a reconciliation that recomputes a closed day after late-arriving offline playback and alerts above a threshold, a denominator assertion inside the query. A fix that lives in a pipeline is different in kind from a resolution to be more careful.
- Close with whether the control has fired since, or how you tested that it would. That single sentence is what separates a fix from an intention, and interviewers ask for it when candidates do not offer it.
Follow-up
- Why didn't your own review catch it? Be specific about what you did check.
- What class of error would that control still not catch, and what would you add next?
- Have you found an error in someone else's published analysis since? How did you raise it?