cswork / libraryOpen practice workspace

Interview guides · 1010data

1010data Data Scientist Interview Questions & Guide 2026

1010data Data Scientist interview guide: the four reported rounds, statistics, ML, coding and behavioral questions, with a 7-day preparation plan.

The 60-second brief

Candidates describe the Data Scientist role at 1010data as work on large datasets: building predictive models, running statistical analysis and producing visualizations and reports that clients and internal teams use to make decisions. The role is described as sitting alongside engineering and product development teams. Candidates do not report specific products, customers or internal tools, so this guide does not guess at them. It concentrates on the skills the role lists: statistical analysis, machine learning, Python or R, and visualization tools such as Tableau or Matplotlib.

This guide is for candidates interviewing for a Data Scientist position at 1010data. It covers the four reported rounds (Phone Screening, Technical Interview, Behavioral Interview, Team Interactions), worked guidance for the reported behavioral question, preparation guidance for the reported statistics, machine learning, coding and case questions, and a seven-day plan. It is built from candidate reports, not from a process the company has published.

Candidates report four rounds spread over roughly 3-5 weeks: a Phone Screening, a Technical Interview, a Behavioral Interview and a Team Interactions round with several team members.

Focus areas

  • Diagnose rebuffering by device, network and POP
  • Measure catalogue breadth beyond head consumption
  • Separate release-calendar spikes from underlying trend

Overview

The reported questions mix four kinds of material. The statistics and probability questions include the assumptions behind linear regression, which statistical methods you use and why, and a function that returns the mean and standard deviation of a list. The machine learning questions include supervised versus unsupervised learning and a case on predicting churn for a subscription service. Coding questions cover sorting large datasets, code performance and a word-anagram check. Case questions ask how you would find trends over time in a dataset and how you cleaned and prepared data on a past project.

The requirements candidates cite are statistical analysis, machine learning, Python or R, and a visualization tool such as Tableau or Matplotlib. Familiarity with Hadoop or Spark, a cloud platform such as AWS or Azure, and A/B testing and experimental design are listed as extras. Deep learning frameworks, natural language processing and big data tools such as Hadoop or Spark are also mentioned as topics that may come up, so know each at overview level. For preparation this means breadth with a defensible depth: state the assumptions behind each method you name, write short functions without autocomplete, and describe a model from problem definition to evaluation.

Candidates mention questions on a challenging project, on making an analysis understandable to non-technical stakeholders, and on resolving a conflict within a team, and they describe a round with several team members. The role is described as collaborative with engineering and product development, so every story you prepare should show the data work itself (what you checked, what you decided) and the people you had to bring along.

Use your own projects as the raw material. Several reported questions ask about your past work: a dataset you analysed, a project where you cleaned data, the statistical methods you reach for most. Build a short inventory of projects with the data, method, tool and a result number for each, and let the question decide which one you tell.

The 1010data interview process

Candidates describe four stages. The format of each is reported only in outline, so use these descriptions to decide what to prepare and confirm the details with your recruiter.

Phone Screening

Candidates report a first call about your background and how it fits the role. The requirements candidates cite for the role are statistical analysis, machine learning, Python or R, and visualization tools such as Tableau or Matplotlib, so organise your summary around where you have used those. Lead with one or two projects rather than a list of employers, and be ready to talk about a dataset you have worked with, including what was awkward about it.

What to demonstrate

  • How your experience maps to the skills candidates cite for the role: statistics, machine learning, Python or R, visualization
  • Whether you can discuss a dataset you have worked with in concrete terms
  • Fit with the role as it is described to you on the call

How to prepare

  • Write a short pitch that names your strongest project, the data it used, the method you chose and the result
  • List where Python or R, a visualization tool and a modelling method each appear in your work, with one example per item
  • Prepare questions about the data the team works with and how the role collaborates with engineering and product development

Technical Interview

Candidates report technical questions on data science skills. The reported questions across the loop fall into statistics and probability, general machine learning, coding fundamentals, and case-style problems such as predicting churn or finding trends in a dataset, and none of them is tied to a particular round. Use this stage to practise those types: write small functions by hand, state the assumptions behind every statistical method you name, and walk through a case from problem definition to metric to model.

What to demonstrate

  • Statistical foundations: the assumptions behind a method and what you do when they fail
  • Working code for a small data task in Python or R
  • Machine learning basics and the choice of approach for a case

How to prepare

  • Practice for the reported statistics questions: write the linear regression assumptions from memory, each with the diagnostic that checks it and the remedy when it fails
  • Practice for the reported coding questions: code the mean and standard deviation function, covering empty input, a single value and the sample versus population divisor
  • Practice for the reported case questions: outline the churn case end to end, with label definition, features as of a snapshot date, a time-based split, a baseline and the metric
  • Practice for the sorting and anagram questions: explain how you would sort a dataset larger than memory, and how you would test a word-anagram check

Behavioral Interview

Candidates report a conversation about your earlier work and how it connects to company values. Reports do not say what those values are, so do not guess; prepare stories that show your own decisions, the data work and the people involved. Behavioral questions candidates report include a challenging project, making analysis understandable to non-technical stakeholders, and a conflict within a team, so have a story ready for each. Keep the same facts and numbers every time you tell a story, because later rounds involve more people.

What to demonstrate

  • Your account of past projects, in your own role and decisions
  • How you communicate analysis to people outside data science
  • How you describe working through disagreement with colleagues

How to prepare

  • Write the challenging-project story as situation, your decisions, trade-offs, result with a number, and what you would change
  • Prepare one conflict story in which you resolved the disagreement with evidence rather than escalation
  • Practise explaining one model or test result in two sentences for a stakeholder who has never seen a confidence interval

Team Interactions

Candidates report meeting several members of the team, with collaboration and fit as the reported aim. Reports do not give the format, so ask the recruiter who you will meet and whether to expect a discussion, a technical exercise or a presentation. Because the role is described as working with engineering and product development, prepare examples of working with people outside your own function.

What to demonstrate

  • How you work with several team members at once
  • Examples of working with colleagues outside your own function
  • Fit with the team, which candidates report as the aim of this round

How to prepare

  • Re-read the numbers and scope in each project story so a second telling matches the first
  • Prepare a cross-functional example: what an engineer or product partner needed, and how you adjusted your analysis
  • Bring questions for the team about the data they work with and how analysis gets used after you deliver it

Where candidates lose points

Listing the linear regression assumptions as four words with no consequence attached.

For each assumption (linearity in the parameters, independent errors, constant error variance, no perfect multicollinearity, errors unrelated to the predictors, and normal errors for small-sample inference) state what breaks, how you check it (residuals against fitted values, a Q-Q plot, Breusch-Pagan, Durbin-Watson, variance inflation factors) and the remedy (transform, robust standard errors, drop or regularise).

Describing a churn model without a precise label, a snapshot date or a time-based split.

Define churn first (cancellation or non-renewal, and any grace period), choose the prediction date and the horizon, build features only from data available at that date, and split by time. Say which metric fits the retention action: precision and recall at the number of customers the team can contact, or PR-AUC, because accuracy is useless when churners are rare.

Answering the mean and standard deviation function with a one-liner that ignores edge cases.

Decide whether the standard deviation is the sample (n minus 1) or population (n) version and say so. Handle an empty list and a single value, and mention a one-pass update such as Welford's algorithm, since the sum-of-squares shortcut loses precision when values are large and close together.

Answering the sorting question with "use quicksort" and nothing about data that does not fit in memory.

Name the in-memory choice and its properties (O(n log n), stable sorts such as Timsort, behaviour on nearly sorted input), then the external version: sort chunks that fit in memory, write the runs to disk and merge them with a k-way heap merge. For a cluster, describe sampling to choose range partitions, shuffling, and sorting each partition locally.

Telling project stories that end at "I built a model", or where the data cleaning had no checks.

End every story with a result, a number and what changed afterwards. For the data preparation story, list the checks you ran (duplicates, missing values, ranges, join row counts), the rule you chose for each problem and what it did to the final numbers.

Questions & practice

Write a function to compute the mean and standard deviation of a list of numbers.

Medium · statistics and probability

Approach

  1. Clarify the contract out loud: population standard deviation divides by n, sample standard deviation by n-1 (Bessel's correction); an empty list should raise an error, and a sample standard deviation of a single value is undefined.
  2. Write a single-pass version with Welford's algorithm: for each x, n += 1; delta = x - mean; mean += delta / n; M2 += delta * (x - mean). Then variance = M2 / (n - 1) or M2 / n, and std = sqrt(variance). O(n) time, O(1) extra space.
  3. Explain why not the shortcut sum(x^2)/n - mean^2: subtracting two large, nearly equal numbers causes catastrophic cancellation and can even return a small negative variance in floating point.
  4. Test with a known case: [2, 4, 4, 4, 5, 5, 7, 9] has mean 5 and population standard deviation 2; also test a constant list (std 0), one element, and handling of NaN or non-numeric input.
  5. Know the library defaults so results line up: Python's statistics.stdev is the sample version and pstdev the population one; numpy.std defaults to ddof=0, while pandas Series.std defaults to ddof=1.

Follow-up

  • How would you combine the mean and variance computed on two separate partitions without rereading the data?
  • How would you change the function to support weights?
  • The data is heavily right-skewed. What would you report alongside or instead of the mean and standard deviation?

Describe an efficient algorithm for sorting large datasets.

Medium · machine learning and modelling

Approach

  1. Start from whether the data fits in memory. In memory, use a comparison sort: Timsort (Python's sorted, stable, close to O(n) on already sorted runs) or merge sort (stable, O(n log n) worst case, O(n) extra space). Plain quicksort averages O(n log n) but its worst case is O(n^2); randomized pivots give an expected O(n log n) on any input, and only an introsort fallback (or median-of-medians pivots) guarantees the worst case.
  2. For integer or fixed-width keys, a non-comparison sort such as radix sort runs in O(n*k) for k-digit keys and can beat O(n log n) on very large inputs.
  3. For data larger than memory, describe external merge sort: read chunks that fit in memory, sort each and write it as a sorted run, then k-way merge the runs with a min-heap, O(N log k) for the merge, streaming output to disk.
  4. In a distributed engine such as Spark, a global orderBy samples keys to build range partitions, shuffles rows to them and sorts within each partition; skewed keys create oversized partitions, which is the usual bottleneck.
  5. Question whether a full sort is needed at all: top-k needs only a size-k heap, O(n log k); grouping needs hashing, not ordering; repeated range queries are better served by an index or a pre-sorted columnar file.

Follow-up

  • Which of these algorithms are stable, and when does stability matter for a data task?
  • How would you find the 100 largest values in a billion-row file with limited memory?
  • One sort key value accounts for 30 percent of the rows. What happens to a distributed sort, and how would you handle it?

What are the assumptions of a linear regression model?

Medium · machine learning and modelling

Approach

  1. List the assumptions precisely: linearity in the parameters, errors with zero mean given the features (exogeneity), independent errors, constant error variance (homoscedasticity), no perfect multicollinearity, and normally distributed errors.
  2. Say what each one buys: under the Gauss-Markov conditions (linearity, full rank, exogeneity, homoscedastic and uncorrelated errors) OLS is the best linear unbiased estimator; normality is not needed for that and matters mainly for exact small-sample t and F inference.
  3. Separate bias from bad standard errors: omitted variables or other endogeneity bias the coefficients, while heteroscedasticity and autocorrelation leave coefficients unbiased but make the usual standard errors wrong.
  4. Give the diagnostics: residuals versus fitted values for nonlinearity and fanning, a Q-Q plot of residuals, Breusch-Pagan for heteroscedasticity, Durbin-Watson for autocorrelation in ordered data, and variance inflation factors for multicollinearity.
  5. Name the remedies: transform variables or add polynomial and interaction terms, use robust (HC) or clustered standard errors, drop or combine collinear features or use ridge regression, and address endogeneity with better controls or an instrument.

Follow-up

  • Does the target variable itself need to be normally distributed?
  • Two features are highly correlated. What happens to the coefficients and to the predictions?
  • Your residuals fan out as fitted values grow. What do you change, and does it alter the coefficient estimates?

Sessionise playback heartbeats into streams under an idle-gap rule

Hard · sessionisation · gaps-and-islands · pandas

heartbeats has profile_id, device_id, content_version_id, heartbeat_ts, playhead_seconds, is_paused and reported_at; clients emit one every 30 seconds while in the foreground. Collapse it into stream rows keyed on (profile_id, device_id, content_version_id) under a 300-second idle gap. Emit started_at, ended_at, max_position_seconds and played_seconds, where played_seconds counts advancing playback only and so excludes pause, seek and rebuffer time. Flag streams whose reported_at trails heartbeat_ts by more than an hour. Then re-run at a 60-second gap and report what moved.

Approach

  1. Sort by the key and heartbeat_ts, take the per-row time delta inside the key with groupby().diff(), mark a new stream where that delta is null or above 300 seconds, and cumulative-sum the flag into a stream ordinal. This is gaps-and-islands, and it is O(n log n) after the sort with no per-group Python loop.
  2. Compute played_seconds from consecutive heartbeats as min(max(playhead_delta, 0), wall_delta), summed inside the stream. The lower clamp discards a backward seek; the upper clamp is what stops a forward seek to the end of a forty-minute episode from crediting forty minutes of playback to one thirty-second interval. Note what this makes the column mean: a rewind and replay inside one stream counts the replayed interval twice, because played_seconds measures time spent advancing the playhead, not distinct coverage of the item.
  3. Accept that the first heartbeat of each stream has no predecessor and contributes nothing, which undercounts by up to one heartbeat interval per stream. Quantify that bias as stream count times 30 seconds against the total instead of leaving it unsaid.
  4. Take ended_at as the last heartbeat_ts, but keep it null when that heartbeat sits within one idle gap of the data cutoff: such a stream may still be running, and giving it an ended_at manufactures a completed stream.
  5. For the offline flag, work on reported_at minus heartbeat_ts rather than on event_date, and report the distinct dates the flagged streams belong to, because that list is the recompute list.
  6. Re-run at 60 seconds and report both the stream count and the change in total played_seconds: the count can only rise, and the total falls by exactly the contributions that now straddle a split.

Worked solution (40 min)

  1. Sort by (profile_id, device_id, content_version_id, heartbeat_ts); compute wall_delta as the grouped heartbeat_ts diff in seconds and pos_delta as the grouped playhead_seconds diff.
  2. new_stream = wall_delta.isna() OR wall_delta above 300; stream_ordinal = new_stream grouped-cumsum; group on the key plus stream_ordinal from here on.
  3. contrib = np.minimum(np.maximum(pos_delta, 0), wall_delta), forced to 0 on the first row of each stream; played_seconds is contrib summed per stream.
  4. Aggregate started_at as min(heartbeat_ts), ended_at as max(heartbeat_ts), max_position_seconds as max(playhead_seconds), and late_offline as max(reported_at minus heartbeat_ts) above 3600 seconds.
  5. Null ended_at where it falls within 300 seconds of the data cutoff.
  6. Re-run with the gap as a parameter at 60 seconds, and diff the two summaries on stream count and total played_seconds.

Expected result: One row per stream whose played_seconds never exceeds ended_at minus started_at in seconds, since each interval contributes at most its wall_delta and the wall_deltas inside a stream telescope to exactly that span. At a 60-second gap the stream count is greater than or equal to the 300-second count, and total played_seconds is lower by exactly the sum of the contributions that now straddle a split.

Verify it yourself

  • Assert played_seconds at or below (ended_at minus started_at) in seconds for every stream; any violation means the clamp against wall_delta was dropped.
  • No stream spans two content_version_ids or two device_ids. Group the heartbeats back by stream ordinal and check nunique equals 1 on both.
  • A hand-built three-heartbeat case with a backward seek (playheads 600, 300, 330 at 30-second spacing) yields played_seconds of 30, not 60 (the wall clock) and not -270 (the raw playhead deltas).
  • The distinct event_date partitions of the late_offline streams are exactly the days that need recomputing, and that set should be small and old rather than spread evenly across the window.
  • Do not assert played_seconds against an item's runtime. The heartbeats input carries no duration_seconds, and the bound does not hold even if you join one: a backward seek followed by a rewatch advances the playhead over the same interval twice inside one stream, and both passes are genuine playback. max_position_seconds is the column that compares against a joined runtime, and then only within the segment-overshoot tolerance of a second or two.

Follow-up

  • A client dies mid-playback and stops reporting. What does its stream look like under this rule, and what does that do to mean stream duration?
  • Which of these numbers determines a per-stream payout, and what does moving the idle gap do to somebody's money?
  • The same profile plays the same track on a phone and a TV at once. Your key separates them. When would you want it not to?

Net revenue per active account-month without fanning out periods

Hard · fan-out · revenue recognition · grain

fct_subscription_period has account_id, period_start_ts, period_end_ts, net_amount_usd and payment_status. fct_stream has account_id, started_at, played_seconds and is_qualified. Compute net revenue per active account-month for March 2026: the numerator is subscription revenue over periods with payment_status in ('paid','retried_paid'), recognised pro rata by the days of each period falling inside March; the denominator is distinct accounts with at least one qualified stream in March. A colleague's draft joins the two tables on account_id, filters streams to qualified March rows, and sums net_amount_usd; its numerator is roughly forty times too large. Explain the factor and write the correct query.

Approach

  1. Name the mechanism and decompose the number. The join is one period to many streams, so each period row is duplicated once per matching stream and its net_amount_usd is added that many times: the naive sum equals the un-prorated period sum weighted by each account's March qualified-stream count, which is the net_amount_usd-weighted mean stream count over the accounts the inner join keeps. The distance from there to the correct numerator has two further parts, in opposite directions — pro-rating shrinks the correct figure, while the inner join has dropped every period belonging to an account that streamed nothing in March. A weighted mean near thirty streams against a pro-rata factor near 0.75 is what puts the draft around forty times high; the number is not arbitrary, which is why it looks plausible enough to ship.
  2. Adopt the rule that produces the fix: two fact tables never meet at row grain. Aggregate each to account grain first and join the aggregates, or — better here — do not join at all, because the numerator and the denominator are independent scalars that share no key.
  3. State the interval convention before writing the arithmetic. Treating periods as half-open [period_start_ts, period_end_ts) makes consecutive periods tile without overlap; overlap_days = GREATEST(0, LEAST(period_end_ts, DATE '2026-04-01') - GREATEST(period_start_ts, DATE '2026-03-01')) in days, and the contribution is net_amount_usd * overlap_days / total_days_of_period. An annual period contributes roughly 31/365 of its amount.
  4. Build the denominator on its own: COUNT(DISTINCT account_id) FROM fct_stream WHERE is_qualified AND started_at >= '2026-03-01' AND started_at < '2026-04-01'. Do not restrict it to paying accounts — the metric deliberately holds subscription and advertising revenue to one engaged-account denominator, and filtering to payers is what makes a tier migration look like two unrelated numbers moving.
  5. State the closing rule with the result: the month must be at least 45 days closed, because refunds and chargebacks are recorded against periods late and an open month always reads high.

Follow-up

  • One account holds two overlapping periods after a plan migration. Whose revenue counts for March, and does your pro-rata still sum to each period's own total?
  • Advertising revenue belongs in this numerator. What grain does it arrive at, and what breaks in your query when you add it?
  • How would you turn this into a monthly time series without rewriting the date literals, and what would you do about periods that span three months?

Sessionise playback into listening sessions with an idle gap

Medium · window functions · gaps and islands · sessionisation

fct_stream has profile_id, stream_id, content_version_id, started_at, ended_at (nullable when the client stopped reporting) and played_seconds. Collapse consecutive streams by the same profile into listening sessions: a new session begins when a stream starts more than 30 minutes after the latest end reached by any of that profile's earlier streams. One profile can play on two devices at once, so streams overlap and 'the previous stream' is not well defined — the idle gap is measured against the running maximum end, not against the row immediately before. Treat a NULL ended_at as started_at plus played_seconds. Return one row per session with profile_id, session_start, session_end, stream_count, distinct content_version_id count and total played_seconds. Use window functions; a self-join on fct_stream is not an acceptable answer.

Approach

  1. In a first CTE compute effective_end = COALESCE(ended_at, started_at + played_seconds * INTERVAL '1 second'). This both fills the abandoned-client case and bounds it, since played_seconds is advancing playback only and cannot run past the true end.
  2. Take prev_max_end = MAX(effective_end) OVER (PARTITION BY profile_id ORDER BY started_at, stream_id ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), and flag is_new_session = (prev_max_end IS NULL OR started_at > prev_max_end + INTERVAL '30 minutes'). LAG(effective_end) is the reflex here and it is wrong under concurrent devices: with A 10:00-12:00, B 10:05-10:10 and C starting 10:45, LAG hands C the end of B at 10:10, so C opens a new session 35 minutes into a session still running until 12:00. The NULL branch is not defensive noise either: the first row of every profile has no preceding frame and would otherwise fall outside any session.
  3. Number the sessions with SUM(is_new_session::int) OVER (PARTITION BY profile_id ORDER BY started_at, stream_id ROWS UNBOUNDED PRECEDING). A running count of boundaries is the gaps-and-islands device. Specify ROWS rather than letting the default RANGE frame apply, because RANGE lumps peer rows that share a started_at into one frame position — the same reason the ORDER BY carries stream_id as a tie-break.
  4. Group by (profile_id, session_number) and aggregate: MIN(started_at), MAX(effective_end), COUNT(*), COUNT(DISTINCT content_version_id), SUM(played_seconds).
  5. The running maximum makes the sessions disjoint in wall-clock time, but it does not make SUM(played_seconds) an elapsed-time figure: two devices playing at once contribute overlapping seconds, so the total can exceed session_end minus session_start. Report it as consumption and say which of the two the requester asked for.

Worked solution (30 min)

  1. Materialise the effective_end CTE and check for rows where effective_end < started_at, which means negative played_seconds or a clock-skewed ended_at and must be quarantined before anything else.
  2. Add the running MAX and the boundary flag; verify that exactly one row per profile has prev_max_end IS NULL and that it is flagged as a session start.
  3. Add the running SUM to number sessions; confirm the numbers are dense and begin at 1 within each profile.
  4. Aggregate and reconcile: SUM(stream_count) across all sessions must equal the input row count.
  5. Run the overlap fixture — one profile with A 10:00-12:00, B 10:05-10:10 and C at 10:45 — and confirm it collapses to one session. With LAG(effective_end) in place of the running MAX it splits into two, and the second session's start, 10:45, sits inside the first session's span.

Expected result: One row per (profile_id, session_number). Session numbers are dense from 1 within each profile, SUM(stream_count) equals the input row count exactly, and a profile's sessions are disjoint in wall-clock time: each session_start exceeds the previous session's session_end by more than 30 minutes.

Verify it yourself

  • Input row count equals SUM(stream_count); a shortfall means rows fell out of the window partition or the NULL branch of the boundary flag.
  • For any profile, each session's session_start exceeds the previous session's session_end by more than 30 minutes. This holds only because the boundary is measured against the running maximum end — under LAG(effective_end) the overlap fixture above produces a session whose session_start, 10:45, precedes the prior session_end of 12:00.
  • Changing the gap from 30 to 60 minutes reduces the session count or leaves it unchanged, and never raises it.

Follow-up

  • Two devices on one profile play simultaneously. Is that one session or two, and what changes in the query for each answer?
  • Re-run at a 10-minute and a 60-minute idle gap. How far does session count move, and which number do you publish and defend?
  • Can SUM(played_seconds) for a session ever exceed session_end minus session_start, and what does it mean when it does?

Given a dataset, how would you identify trends over time?

Medium · product sense and metrics

Approach

  1. Pin the grain and the clock first: one row per entity per period, a fixed timezone, and a complete calendar (fill missing periods with explicit zeros or NaN) so gaps do not read as drops.
  2. Separate trend from seasonality before calling anything a trend: compare year over year or week over week at the same weekday, or decompose with STL (seasonal-trend decomposition using Loess) into trend, seasonal and residual parts.
  3. Smooth with a rolling mean or median and state whether the window is trailing or centred; a trailing window lags turning points by about half its length, while a centred one cannot be computed for the most recent periods.
  4. Test the trend rather than eyeballing it: a Mann-Kendall test for a monotonic trend, or a regression on time with Newey-West (HAC) standard errors, because autocorrelated residuals make plain OLS standard errors too small.
  5. Check that the trend is not composition or definition change: split by segment to rule out a mix shift (Simpson's paradox), and line up breaks against logging releases or metric-definition changes with a changepoint method.

Follow-up

  • How would you tell a genuine level shift from an instrumentation change that happened on the same date?
  • The aggregate is flat but every segment is rising. What explains that, and which number do you report?
  • How would you extend this into a forecast, and how would you validate it without leaking future data?

How would you approach predicting customer churn for a subscription service?

Medium · product sense and metrics

Approach

  1. Define the label before any model: for a subscription, churn is usually cancellation or non-renewal within a fixed horizon after a prediction date, and voluntary churn should be separated from involuntary churn caused by failed payments.
  2. Build features as of the prediction date only: tenure, plan, usage level and its recent trend, billing failures, support contacts. Exclude anything recorded after that date, such as visits to the cancellation flow, to avoid leakage.
  3. Split by time (train on earlier snapshots, test on later ones), start with a logistic regression baseline, then try gradient-boosted trees and keep the gain only if it holds on the later period.
  4. Choose metrics for an imbalanced, action-driven target: PR-AUC, recall or lift in the top decile that the retention team can actually contact, and calibration if scores feed an expected-value calculation.
  5. Say what the score is for: a high churn risk does not mean an offer will change the outcome, so targeting is better served by an uplift model or a randomized retention test; survival analysis (Cox model) handles time-to-churn with censored active users.

Follow-up

  • How would you decide which customers receive a retention offer, and how would you measure whether the offer worked?
  • How do you handle customers who joined recently and have not yet reached the end of the horizon?
  • After a price change, model precision drops. What do you check first?

Describe a project where you had to clean and prep data for analysis.

Medium · product sense and metrics

Approach

  1. Open with the decision the analysis supported and the raw state of the data: sources, row counts and the intended grain (one row per what), so the cleaning steps have a reason.
  2. Walk through profiling before fixing: null rates per column, duplicate keys at the intended grain, value ranges, inconsistent units or currencies, timezone mismatches, and join keys that failed to match.
  3. For each issue, give the rule and its justification: deduplicate on a stated key with a stated tie-break, decide drop versus impute based on whether missingness looks random or tied to the outcome, and separate data-entry errors from genuine extremes before treating outliers.
  4. Make it reproducible and checkable: scripted steps instead of manual edits, assertions on row counts and key uniqueness after every join, and a log of how many rows each rule changed.
  5. Close with impact in numbers: how much the headline result moved between the raw and cleaned data, and the one check you would run earlier next time.

Follow-up

  • How did you confirm that dropping or imputing rows did not bias the result?
  • Which of these checks did you automate for the next data refresh, and where do they run?
  • What would you have done if the source owner disagreed with your fix?

How do you optimize your code for performance?

Medium · product sense and metrics

Approach

  1. Measure before changing anything: time the full job, then profile with cProfile or line_profiler (or Rprof / profvis in R) to find the few lines that dominate runtime.
  2. Fix the algorithm first: replace a nested-loop match, O(n*m), with a dict or set lookup or a join, O(n+m) expected; replace repeated list membership tests with a set; sort once instead of inside a loop.
  3. Vectorize data work: NumPy or pandas column operations instead of iterrows or apply with axis=1, which call Python code once per row; in R, vectorized functions or data.table instead of growing objects in a loop.
  4. Cut memory and I/O: read only needed columns, set compact dtypes (category for low-cardinality strings, downcast numerics), use a columnar format such as Parquet, and push filters and aggregations into SQL so less data reaches Python.
  5. Scale out only when one machine is not enough: multiprocessing for CPU-bound Python (threads in standard CPython are limited by the GIL), or Spark for data larger than memory. After every change, compare with the original output: exact equality for integer and key columns, a stated tolerance (np.allclose, or pandas.testing.assert_frame_equal with rtol) for floats, since vectorized sums reorder additions.

Follow-up

  • When would you decide a slow piece of code is not worth optimizing?
  • How would you process a file that does not fit in memory on your laptop?
  • Your vectorized version is faster but returns slightly different numbers. How do you investigate?

What statistical methods do you frequently use, and why?

Medium

Approach

  1. Pick three or four methods you actually use and give each a trigger, not a list: Welch's t-test for comparing means with unequal variances, a two-proportion z-test or chi-square test for rates, and Mann-Whitney when you care whether one group tends to produce larger values (it tests stochastic ordering, not a difference in means or medians).
  2. For a mean comparison on heavily skewed data, use a bootstrap interval or Welch's test with a large n instead of Mann-Whitney. Include estimation, not only testing: confidence intervals around every reported difference, and the bootstrap for statistics without a simple formula such as medians, ratios or percentiles.
  3. Cover regression as the workhorse: linear or logistic regression to control for covariates and read effect sizes, with the assumption you check each time (residual plots, multicollinearity).
  4. For experiments, mention a power analysis before launch to set sample size, and a multiple-comparison correction (Bonferroni, or Benjamini-Hochberg for false discovery rate) when many metrics or segments are tested.
  5. Anchor each method in one project: the question, why that method fit the data, the assumption you verified, and the decision the result changed.

Follow-up

  • When would you choose the bootstrap over a closed-form confidence interval?
  • How would you explain a p-value of 0.03 to a non-technical stakeholder without misstating it?
  • What inputs do you need to compute the sample size for an A/B test?

Define success before a startup-latency infrastructure change

Easy · metric design · quality metrics · guardrails · mix effects

An infrastructure change is expected to cut startup_latency_ms on tv_app playback. You must define success before the rollout. Available: fct_stream (startup_latency_ms, rebuffer_seconds, played_seconds, peak_bitrate_kbps, end_reason, start_source, device_type, cdn_pop, network_type). Propose one primary outcome metric, the latency statistic you will report and at which quantile, and two guardrails. State how you will stop a mix shift across device_type and cdn_pop from reading as an improvement, and name one way a latency win could be bought rather than earned.

Approach

  1. Separate the input from the outcome out loud: startup_latency_ms is the thing the change moves, but success is whether consumption improved, so the primary metric is qualified hours per active account-week on tv_app and latency is the mechanism metric that explains it.
  2. Choose the latency statistic for the shape of the distribution: startup latency is right-skewed with a long tail from cold caches and poor networks, so report p95 and p99 rather than a mean, because the mean moves with tail mass that a handful of streams contribute and the tail is where abandonment lives.
  3. Kill the mix effect by construction: compute the latency quantiles inside (device_type, cdn_pop, network_type) cells and aggregate with cell weights fixed from a pre-period, so that a rollout reaching wifi and well-provisioned points of presence first cannot show a win that is entirely composition.
  4. Guardrail one on how a latency win is bought: peak_bitrate_kbps and the rebuffer ratio, sum(rebuffer_seconds)/sum(played_seconds) with played_seconds = 0 rows excluded from both sides, because starting playback at a lower ladder rung lowers time to first frame and costs picture quality instead.
  5. Guardrail two on what the ratio hides: start-failure rate over streams with end_reason = 'playback_error' and played_seconds = 0, counted separately, since a point of presence that fails before first frame contributes to neither side of the rebuffer ratio and would otherwise report perfectly.
  6. State the exclusion: cut or exclude start_source = 'resume', because resumed playback starts from a warm buffer and a rollout that shifts the resume share moves pooled latency without any engineering change.

Worked solution (20 min)

  1. Write the primary metric with numerator, denominator and window, restricted to device_type = 'smart_tv' and its streaming-stick equivalents, and state that it is the outcome the change is judged on.
  2. Specify the latency readout: p95 and p99 of startup_latency_ms computed within (device_type, cdn_pop, network_type) cells, aggregated with pre-period cell weights, reported daily against the same cells pre-rollout.
  3. Construct one numerical counterexample showing the mix trap: two points of presence with unchanged within-cell latency but a shift in traffic share between them, and compute that the pooled mean moves while every cell is flat.
  4. Write the two guardrails: peak_bitrate_kbps distribution plus rebuffer ratio with the played_seconds = 0 exclusion stated, and start-failure rate over end_reason = 'playback_error'.
  5. State the exclusion rule for start_source = 'resume' and the reason, and write the one-line ship rule combining primary and guardrails.

Expected result: Primary: qualified hours per active account-week on TV devices. Mechanism metric: p95 and p99 startup_latency_ms within (device_type, cdn_pop, network_type) cells with fixed pre-period weights. Guardrails: peak_bitrate_kbps together with the rebuffer ratio excluding played_seconds = 0, and a separately counted start-failure rate over end_reason = 'playback_error'. The bought win is opening at a lower bitrate rung.

Verify it yourself

  • The worked mix counterexample shows every cell flat while the pooled mean moves, which confirms the weighting is doing real work rather than being ceremony.
  • Streams with played_seconds = 0 appear in exactly one metric, the start-failure rate, and in neither side of the rebuffer ratio.
  • The bitrate guardrail is stated as a distribution, not a mean, so a shift of the low tail onto a lower ladder rung is visible.
  • Resume-sourced streams are either excluded or reported as their own cut, so a change in resume share cannot move the headline latency.

Follow-up

  • p99 falls but the mean rises. What do you conclude, and which cut would you look at first?
  • The change only reaches accounts on app versions above a floor. How does that change what your comparison can claim?
  • How large a latency improvement would you need before you expect qualified hours to move at all, and how would you size that before running it?

Completion rate fell while every duration decile improved

Medium · simpsons-paradox · mix-shift · decomposition

Duration-normalised completion rate — qualified streams with completion_ratio >= 0.9 over qualified streams with non-null duration_seconds — fell from 0.41 to 0.36 across six weeks. Computed inside each (content_type, duration decile) cell, every cell is flat or up. Using fct_stream (completion_ratio, is_qualified, content_version_id, started_at, start_source) joined to dim_content_version (content_type, duration_seconds, catalogue_added_ts, parent_id), reconcile the two numbers and say which the content team should act on. Deliverable: a decomposition separating mix shift from within-cell change, with the size of each in completion-rate points.

Approach

  1. Name the mechanism before computing anything. completion_ratio is mechanically decreasing in duration_seconds, so the global rate is a duration-mix-weighted average of cell rates and moves whenever the weights move with every cell rate held fixed. That is not a paradox, it is the aggregation doing what it was always going to do.
  2. Run the two-term decomposition: total change equals sum over cells of (w_new - w_old) * r_old for mix, plus sum over cells of w_new * (r_new - r_old) for within. Those two terms are exhaustive — the sum telescopes back to the observed change exactly — because this arrangement already carries the whole weight-by-rate interaction inside the within term. There is no third quantity to add. State the convention anyway: the mirror arrangement, mix at r_new plus within at w_old, is equally exact and parks the interaction in mix instead, and the two attributions differ by exactly sum (w_new - w_old) * (r_new - r_old) whenever weights and rates moved together. Report in completion-rate points so the argument is about magnitudes rather than adjectives.
  3. Find which weights moved and why. Cell share of qualified streams by week, joined to catalogue_added_ts and parent_id, will usually name a season of long episodes, a podcast push, or a shift in content_type as the cause within one query.
  4. Distinguish a mix shift the product caused from one the calendar caused, by cutting the weight change by start_source. A ranker that began surfacing long-form is a product decision with a durable consequence; a release landing in the window reverses by itself, and the two warrant opposite responses.
  5. Answer what was actually asked. The within-cell term is the quality signal and it is flat to up, so the content team should not chase it. The headline needs the fixed reference-month weights the metric definition already specifies, and the unweighted version should come off the dashboard rather than be explained every six weeks.

Follow-up

  • Fixed reference weights stop the mix moving the number, but a genuine, permanent shift toward long-form then never appears. How do you cover that gap?
  • The deciles are cut from some population. What happens to the decomposition if the decile boundaries themselves move between the two periods?

Suggested practice checklist

The plan follows the reported question mix: statistics first, then modelling cases, trend analysis with charts, coding, data preparation, and finally the project and team stories.

Day 1 — Project inventory and the opening pitch

  • List four projects with the dataset, the method, the tool (Python or R, plus any visualization tool) and one result number each
  • Write a short opening pitch around your strongest project and rehearse it aloud
  • Write the full challenging-project story: situation, your decisions, trade-offs, result and what you would change

Deliverable: A four-row project inventory, a written opening pitch, and the challenging-project story in full.

Day 2 — Statistics foundations

  • Write the linear regression assumptions with the diagnostic and remedy for each
  • Code the mean and standard deviation function twice, with the naive formula and with a one-pass update, and test both against NumPy with the divisor stated
  • Write a one-page sheet on the statistical methods you actually use: when each applies, its assumption and the alternative when the assumption fails

Deliverable: An assumptions table, a tested function with edge cases, and a method sheet with five entries.

Day 3 — Churn case and machine learning basics

  • Write the churn plan: churn definition, snapshot date, prediction horizon, features, time-based split, baseline and metric
  • Build a logistic regression baseline and a gradient-boosted model on a public or synthetic dataset and compare PR-AUC and recall at a fixed contact budget
  • Write a short explanation of supervised versus unsupervised learning, with one example of each from your own work

Deliverable: A one-page churn plan, a notebook comparing the two models, and the supervised versus unsupervised explanation.

Day 4 — Trends over time, charts and aggregates that mislead

  • Take a time series, aggregate it weekly, decompose it into trend, seasonal and residual parts, and decide whether the trend is real
  • Plot the decomposition in Matplotlib (or build the chart in Tableau), then write a two-sentence explanation of it for a non-technical stakeholder
  • Write a short experiment design for an A/B test: metric, unit, sample size and what would make you stop

Deliverable: A decomposition notebook with its chart and stakeholder explanation, a one-page A/B test design and written solutions to the two drills.

Day 5 — Coding and performance

  • Write an external merge sort sketch: sorted chunks to disk, then a k-way heap merge, and note how a Spark sort partitions data
  • Implement the anagram check two ways (sorted comparison and a character count) and state the complexity of each
  • Profile a slow pandas function, replace the loop with a vectorised version and record the speedup

Deliverable: Working sort and anagram code, a before-and-after profile of the pandas function and the solved drill.

Day 6

  • Write up a data cleaning project as profiling checks, the rule chosen for each problem and the effect on final numbers
  • Build a reusable cleaning function with assertions on row counts, duplicates and value ranges

Deliverable: A cleaning write-up with its checks, a tested cleaning function and two solved SQL drills.

Day 7 — Behavioral and team mock

  • Prepare a team conflict story and an answer on making an analysis understandable to non-technical stakeholders
  • Run a full mock, delivering your project story to a friend who interrupts, then compare numbers with your inventory for consistency

Deliverable: Two written stories, drill responses and a note listing any number or scope that changed between tellings.

Project stories and team questions

Reported behavioral questions ask for a challenging project, a team conflict and how you make analysis understandable to non-technical stakeholders. The Data Scientist role is described as collaborative with engineering and product development, so each story should contain the analysis itself (what you checked, what you chose) and the people you worked with, and end with a result.

  1. How do you ensure that your analysis is understandable to non-technical stakeholders?
  2. Can you provide an example of how you handled a conflict within a team?
  3. Describe a time when you analyzed a dataset and what insights you derived from it.
  4. Tell me about an analysis of yours that turned out to be wrong or incomplete, how you found out, and what check you added afterwards.

Describe a challenging project you worked on and how you managed it.

Medium · behavioural and stakeholder questions

Approach

  1. Choose a project whose difficulty you can name in one sentence: an ambiguous question, data that did not match what stakeholders assumed, a scale limit, or two stakeholders wanting different things. "It was large" is not a difficulty. Open with the situation, your role and the decision that had to be made, in two or three sentences.
  2. Spend most of the answer on how you managed it: how you scoped, what you cut, the order of work (baseline first, refinements after), how you kept stakeholders informed, and one decision where you weighed two real options and picked one for a stated reason. Use "I" for your decisions and "the team" only for what was shared.
  3. Put the data science in the story: which data, which method and why that method over the alternative, how you validated it (holdout, sensitivity check, comparison with a simple baseline), and where it nearly went wrong. A story with no analysis in it reads as project coordination.
  4. Close with the result as a number or a changed decision, say which part you can attribute to your work, and give one thing you would do differently that is a choice you made, not a constraint you were handed. The answers that sink are the ones that stop at "I built a model", blame the data, or retell the whole timeline with no decision in it.
  5. Keep two versions of the story, a short one for a screening call and a longer one for rounds with several people, with identical facts and numbers in both.

Follow-up

  • What was the hardest trade-off in that project, and what did you give up?
  • How do you know the improvement came from your work and not from something else changing?
  • If you started again with what you know now, what would you do first?

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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?

Arbitrate three requests for the same analyst week

Easy · prioritisation · stakeholder management · scoping

Three requests land on the same Monday for the same week of your time. Growth wants trial-to-paid conversion recut by signup_channel before a budget meeting on Thursday. The playback team wants rebuffer ratio cut by cdn_pop, device_type and network_type after a bitrate ladder change went out Friday. Catalogue wants a displacement estimate for a title that arrived on a rights window last month. You have roughly four working days. Deliverable: your ordering with the criterion behind it, what you tell the two people who do not get the week, and one thing you ship to all three by Tuesday.

Approach

  1. The probe is your criterion, not your ordering. Rank by how reversible the pending decision is and how fast the evidence decays, and say so; ranking by requester seniority is the default answer and reads as one.
  2. Put the playback request first on those grounds: a bitrate ladder change is live and degrading real playback now, and rebuffer ratio is defined on a trailing 24 hours, so the evidence is perishable and the query is small. Cheap and perishable beats expensive and durable.
  3. Ask each requester what they do differently at each plausible answer. The trial-to-paid recut is a defined tree metric — trailing 28 days, read at trial length plus 21 days so dunning retries have resolved — so it is largely a filter plus an hour confirming that unattributed stays its own line rather than being reallocated pro rata.
  4. Treat the displacement estimate as the only genuinely new analysis and scope it rather than squeezing it: it needs a matched comparison group and a pre-registered window, because consumption of the rest of the catalogue moves on the release calendar regardless. Defer it with a date and a stated shape, never silently.
  5. Reply to all three in one thread with the ordering and the criterion visible, so a stakeholder who disagrees argues with the criterion instead of escalating about their position in the queue.

Follow-up

  • The catalogue lead escalates to your manager on Tuesday. Walk me through that conversation.
  • How does the ordering change if the bitrate ladder change was already rolled back on Saturday?
  • All three deadlines are genuine and none can move. What do you actually cut?