A Data Scientist at Moloco operates at the intersection of machine learning engineering, statistical modeling, and product strategy. Moloco works in operational machine learning and advertising technology, which means its data science team is responsible for driving the algorithms that power real-time bidding (RTB), ad recommendation systems, and performance optimization at large scale. Every day, Moloco's systems process billions of ad requests, requiring data scientists to build models that make highly accurate predictions in milliseconds.
In this role, your work directly impacts Moloco's core products, such as the Moloco Cloud DSP (Demand-Side Platform) and its retail media platforms. Whether you are working in Product Analytics, Ads Integrity, or Creatives, you will focus on optimizing ad spend, maximizing return on ad spend (ROAS) for advertisers, and ensuring the quality and safety of the ads delivered across Moloco's network. You will not merely analyze data to generate static reports; you will design the mathematical frameworks, experimentation protocols, and predictive systems that keep Moloco's platform competitive.
This position is ideal for individuals who thrive on high-dimensional data, complex mathematical challenges, and rapid product iteration. Joining the team means tackling problems that combine auction theory, game theory, and deep learning. It is a highly rigorous environment where technical execution and mathematical precision are prioritized, offering you the chance to see your models directly influence global digital commerce.
Resume Screen
reportedData Scientist covers at least four different jobs: experimentation, product analytics, causal work on observational data, and applied modelling that ships into a system. A screening call is the cheapest place to find out which of them is being hired for, and doing that diagnosis openly reads as senior rather than fussy. Ask what the last few pieces of work on the team actually were, and roughly how a week splits between querying, modelling and stakeholder time. Then say which parts of that you have done and which you have not. Claiming the whole range is the fastest way to be caught one round later.
What to demonstrate
- Whether you can distinguish the flavours of the role and locate your own experience inside one of them honestly
- Whether you name what you have not done instead of stretching to cover every line of the posting
- Whether your hard constraints (notice period, location, work authorisation, level) surface now rather than at offer stage
How to prepare
- Map the last two years of your time into rough percentages across query writing, experiment design, modelling and stakeholder work, so a question about scope has a real answer
- Mark every responsibility in the posting as done, adjacent or new, and prepare one sentence for each adjacent item naming the closest thing you have actually built
- Decide which logistics are non-negotiable before the call so you can state them in one sentence rather than negotiating live
Online Assessment
reportedA handful of shapes account for most of what gets asked in this format: a ranking or deduplication inside groups, a running or rolling total, a period-over-period comparison, and a cohort tracked forward over time. Recognising the shape quickly is most of the speed here; deriving it from scratch while a clock runs is where the time goes. Know that a window function keeps every row while a GROUP BY collapses them, and know which one the question needs. If the exercise is in Python instead of SQL, the same shapes arrive as groupby with transform, shift and merge, and the same grain mistakes are available.
What to demonstrate
- Whether you reach the right construct without a detour, such as ROW_NUMBER over a partition to deduplicate instead of a self-join against a MAX subquery
- Whether you know what your window frame actually is, since adding ORDER BY inside OVER changes the default frame and silently changes a running total
- Whether the thing runs. A near-miss that throws an error scores below a plainer query that returns the right rows.
How to prepare
- Write each of the four shapes once from memory against a small schema and keep the working version somewhere you will reread it: dedupe with ROW_NUMBER, a running total, a month-over-month change with LAG, and a retention table
- Compute one running total twice on data with tied timestamps, once on the default frame and once with ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, and look at where the two disagree
- If Python is on the table, rebuild the dedupe and the running total with groupby and cumsum, then assert the two implementations return identical rows
Technical Screening
reportedBefore anything else, this round is a reading test. You are given a small schema and a question phrased in business language, and most of the difficulty sits in the gap between them. Who counts as an active user, does a refunded order still count as an order, is that date column an event time or a load time. Weak answers start typing immediately and compute something precise about the wrong population. Strong ones pin the definition in one sentence, name the column that encodes it, then write the query. On a timed assessment with nobody to tell, write the definition in a comment anyway.
What to demonstrate
- Whether an ambiguous term becomes a specific column and filter before any computation happens
- Whether you read the schema for keys and cardinality rather than only for column names
- Whether the result answers the question at the grain it was asked at, per user or per session or per day
How to prepare
- Take three metrics you already use and write down the exact filter and exact grain behind each, then practise stating one of them in a single sentence out loud
- On a schema you have never seen, spend the first minute writing what one row of each table means and which key it is unique on, then predict which joins can duplicate rows
- Rehearse a version where the definition changes halfway through, and edit the query you have instead of starting over
Onsite Interview
reportedA day of back-to-back interviews samples your floor, not your ceiling. Four hours in, the habits that carry a good answer are the first to go: restating the question before solving it, asking what the data would have to look like, checking a number before quoting it. What the day decides is whether the tired version of you is still someone to leave alone with an ambiguous problem. The round that sinks a candidate is usually not the hardest one. It is the one immediately after the round that went badly.
What to demonstrate
- Whether the late rounds get the same clarifying questions as the first one, or whether you start answering immediately to save effort
- Whether a weak answer stays in the room it happened in, instead of following you into the next conversation as apology or distraction
- Whether the quality of your questions holds up, since fatigue removes curiosity about the problem before it removes knowledge of the method
How to prepare
- Rehearse the length, not just the content: book four mock interviews of different types in one afternoon with short gaps, because the one you need to observe is the fourth
- Put the two or three questions you ask at the start of any problem on a card in front of you, so that under fatigue it is a habit you run rather than a decision you make
- Decide in advance what the gap between rooms is for: water, one line of notes on anything you promised to follow up, and an explicit close on the round that just ended so it does not travel
- Prepare a different closing question for each interviewer, so the end of a long day does not produce the same one four times
2 candidate reports. Individual accounts describe a particular role and hiring cycle.
Moloco Machine Learning Engineer Interview Experience — Ranking Design, Two Pointers, and ML Fundamentals
The phone screen was a conversation with the hiring manager that included a project deep dive and behavioral questions. The onsite included another project deep dive and a system design question related to ranking. The coding question used two pointers to find the longest consecutive run of 1s. The follow-up asked how to handle an especially long input across multiple machines. I was also asked a…
Read full experienceMoloco Data Scientist Interview Experience — A SQL and Open-Ended EDA Phone Screen, Rejected Days Later
Sharing an interview report for a niche ad company. A recruiter reached out to me on LinkedIn about an opening on a Product Analytics Data Science team. Before the interview they sent over a prep doc saying the technical phone screen would cover SQL + Python/R, with the focus on data manipulation, not modeling. The prep doc also mentioned they might ask about past project details and what I learn…
Read full experiencePracHub editorial advice for the preparation topics above.
Randomising users into treatment and control while both arms draw from the same campaign budget.
The arms compete in the same auctions and against the same budget, so treatment winning more impressions directly starves control, and the measured gap includes that cannibalisation rather than only the ad effect. The test looks methodologically clean, the randomisation is genuinely valid, and the lift is still partly manufactured. This is an interference violation, not a randomisation failure, so checking balance on covariates will not catch it. The fixes are to randomise at a unit that contains the budget, such as a geographic market, or to give each arm its own budget and its own pacing, and then be explicit that you are now comparing two separately-budgeted campaigns.
Comparing conversion rates between users who were exposed to an ad and users who were not.
Exposure is the output of a targeting and bidding system that ranks users by predicted conversion propensity, so the exposed group has a higher baseline conversion rate before the ad renders. The measured difference therefore estimates the quality of the targeting model, not the effect of the advertising, and it is biased upward by an amount that grows as the model gets better. The valid control is the set of users the system would have shown the ad to but deliberately did not, captured through a ghost-bid or public-service-announcement holdout that records auction wins without serving the advertiser's creative. Anyone who proposes matching or covariate adjustment to fix this should be asked what unobserved feature the bidder used that the matching model does not have.
Naming a model class before naming the deployment constraints
Set out the latency budget, the label delay, the retraining cadence, the interpretability requirement and the number of labelled examples, then pick the model that fits them. A boosted-tree answer to a problem where each decision must be explained to the affected user is a well-executed answer to the wrong question.
Ignoring interference between units in a marketplace experiment
Ask whether one unit's treatment can change another unit's outcome through shared inventory, a matching pool, a social graph or a common budget. Where it can, randomise at a level that contains the spillover, such as region or time slice, and say explicitly what that costs you in statistical power.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Calculate the optimal bidding strategy in a real-time auction market g…
Calculate the optimal bidding strategy in a real-time auction market given a competitor's probability distribution of a bidding price.
Approach
- Say what the estimate is of, and over what population it generalises.
- Translate the result into the decision it informs, in one plain sentence.
- Write down the assumption the method needs before you use the method.
Follow-up
- How would you explain this result to someone who does not know statistics?
- What sample size would you need to detect an effect half this size?
How would you design an end-to-end machine learning system to detect a…
How would you design an end-to-end machine learning system to detect and mitigate ad fraud (invalid traffic) in real-time?
Approach
- Frame the prediction: the label, the moment of prediction, and the action it triggers.
- Say how the offline result would be validated online before it is trusted.
- Check what information would not exist at prediction time, and exclude it.
Follow-up
- How would you choose the decision threshold, and who owns that choice?
- Where could label leakage enter this setup?
Simulate bid shading profit under first-price auction clearing
auctions holds 200,000 backtest rows for one line_item_id: predicted_cvr (per impression), floor_price_cpm_usd (nullable when the floor is undisclosed), and best_competing_cpm_usd, the highest competing bid, observable only in this sample. Clearing is first price. The advertiser values a conversion at $40. For shading factors s from 0.50 to 1.00 in steps of 0.05, the submitted bid is s * 1000 * 40 * predicted_cvr and the line item wins when that bid clears both the floor and the best competing bid. Return win rate, spend, conversions and profit per thousand auctions, the profit-maximising s, and an uncertainty estimate for it.
Approach
- Separate what is deterministic from what is random before writing any sampling code. Given the bid, winning is deterministic and the price paid under first-price clearing is exactly the submitted bid, so the only stochastic element is whether a won impression converts. Use the closed form sum of predicted_cvr over wins for the expected conversion count and reserve Monte Carlo for the profit distribution, not the mean.
- Vectorise across s: build the bid vector once, then evaluate the 11 shading factors as numpy operations over the full array. A per-row Python loop over 200,000 rows by 11 factors is about two million iterations and turns a 3-second job into minutes.
- Make the win condition explicit and handle nulls deliberately: treat a null floor as no floor, require bid > best_competing_cpm_usd, and require bid >= floor_price_cpm_usd. Ties at exactly the floor behave differently from ties against a competitor, and the convention should be stated rather than inherited from a comparison operator chosen by accident.
- Compute profit per thousand auctions as (40 * conversions - sum of bid/1000 over wins) / n * 1000. Spend must use your own submitted bid, not the competing bid: under first-price clearing every inframarginal win, one you would have taken at a lower bid, now costs more, and that term is what creates an interior optimum.
- Get uncertainty by bootstrapping auction rows, recomputing the whole curve per replicate, and reporting the distribution of the argmax s rather than only a confidence band around the profit level. The decision uses the argmax, and it is far less stable than the profit curve it comes from.
- State the backtest's limit honestly: best_competing_cpm_usd was observed while you bid what you actually bid. If your change alters competitors' behaviour or triggers exchange-side floor adjustments, the counterfactual does not hold and the curve overstates the achievable gain.
Worked solution 35 min
- Precompute value_cpm = 1000 * 40 * predicted_cvr and floor_eff = floor_price_cpm_usd filled with -inf.
- For each s: bid = s * value_cpm; win = (bid > best_competing_cpm_usd) & (bid >= floor_eff); spend = bid[win].sum()/1000; expected conversions = predicted_cvr[win].sum().
- Profit per thousand auctions = (40 * conversions - spend) / n * 1000; assemble the 11-row curve with win rate = win.mean().
- Bootstrap: resample row indices with replacement 500 times, recompute the curve on each replicate, and collect the argmax s plus a percentile band on profit at the point-estimate optimum.
- Optionally draw Bernoulli(predicted_cvr) on won rows for one replicate set to show the profit distribution around the expectation, and confirm the sampled mean matches the closed form.
Follow-up
- Raising this line item's bid also raises the clearing price faced by another line item in the same account bidding on overlapping supply. How does that change the optimum, and how would you detect it in the logs?
- predicted_cvr over-predicts by 15% at the head of the distribution. Which way does the optimal s move, and roughly how far?
- How would you run this as a live test instead of a backtest, and what is the randomisation unit given that both arms draw on one budget?
Write a SQL query to calculate user retention metrics, specifically id…
Write a SQL query to calculate user retention metrics, specifically identifying the percentage of users who interacted with an ad on day one and returned to purchase on day seven.
Approach
- Check whether any join is one-to-many before aggregating, or the sums inflate.
- Say which table is the grain you start from, and join outward from it.
- Handle the rows that do not match: a LEFT JOIN with a NULL check is usually the question.
Follow-up
- How would you verify this result without re-running the same query?
- What breaks if events arrive late or out of order?
Implement a custom function to perform matrix multiplication from scra…
Implement a custom function to perform matrix multiplication from scratch without using high-level libraries, ensuring optimal memory utilization.
Approach
- Compute rates by summing numerator and denominator separately, never by averaging rates.
- 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.
Follow-up
- What breaks if events arrive late or out of order?
- How does the query change if the join becomes one-to-many?
Given a dataset of ad impressions and clicks, write a Python program u…
Given a dataset of ad impressions and clicks, write a Python program using numpy or pandas to calculate the click-through rate (CTR) grouped by campaign and device type.
Approach
- Compute rates by summing numerator and denominator separately, never by averaging rates.
- Handle the rows that do not match: a LEFT JOIN with a NULL check is usually the question.
- State the window function and its partition and ordering out loud before writing it.
Follow-up
- What breaks if events arrive late or out of order?
- How would you verify this result without re-running the same query?
Attributed cost per action without double counting spend or credit
attribution_credit holds one row per (conversion_id, touchpoint_id, attribution_model, model_version), with touchpoint_ts, line_item_id, credit_fraction, credited_value_usd and click_window_days, the model's click lookback in whole days, which is constant within one (attribution_model, model_version) pair; credit_fraction sums to 1.0 across a conversion's touchpoints within one model and one version. ad_impression has line_item_id, served_ts, billed_price_micros_usd and is_billable. For a UTC date range, return per line_item_id and day: spend, attributed conversions, attributed CPA, attributed ROAS, and a maturity flag derived from the model's own click window, for one named attribution_model and model_version. Both sides must land in the same daily buckets. State which timestamp each side is bucketed on and why.
Approach
- Pin attribution_model AND model_version before aggregating anything. The table stores several models, and several versions of each, for the same conversion. An unfiltered SUM(credit_fraction) therefore reports conversions multiplied by the number of (model, version) pairs present, which is usually a clean integer multiple and so survives a sanity glance.
- Bucket credit on touchpoint_ts, never on conversion_ts. Spend is incurred when the impression served, so aligning the outcome to the touchpoint puts numerator and denominator on the same cohort. Bucketing on conversion_ts shifts every conversion forward by a variable lag and produces day-over-day CPA swings that are pure bucketing artefacts and cannot be corrected downstream.
- Aggregate the two sides in separate CTEs at their own grain, then FULL OUTER JOIN on (line_item_id, day). Joining ad_impression to attribution_credit directly matches each impression against every credit row that references it and multiplies spend by the path count.
- Use a full outer join deliberately: a day with spend and no credit is a genuine CPA of infinity worth surfacing, and a day with credit and no spend means the touchpoint predates the spend window, which is a signal about window choice rather than a row to discard.
- Attributed conversions is SUM(credit_fraction), a fractional quantity, not COUNT(*). Counting rows credits one conversion once per touchpoint and inflates the total by the mean path length, which makes CPA look far better than it is.
- Flag recent days as immature. Spend is final within hours while credit keeps arriving for the model's click_window_days plus ingestion lag, so the most recent click_window_days plus roughly two days have CPA biased high and ROAS biased low. Read that window off the filtered rows rather than hard-coding a number: click_window_days is constant within a pinned (attribution_model, model_version), so MAX(click_window_days) over the pinned filter is a read of that constant. Verify the precondition first, because if MIN and MAX differ the column is not constant, the flag is being computed against a window that does not exist, and it has to come from the model configuration instead.
Worked solution 40 min
- Run SELECT attribution_model, model_version, MIN(click_window_days), MAX(click_window_days), COUNT(*) FROM attribution_credit GROUP BY 1, 2 first, so you know what is actually in the table before filtering to one pair, and so you have checked that the window really is constant inside the pair you are about to pin.
- Build credit AS (SELECT line_item_id, (touchpoint_ts AT TIME ZONE 'UTC')::date AS d, SUM(credit_fraction) AS conv, SUM(credited_value_usd) AS value, MAX(click_window_days) AS win FROM attribution_credit WHERE attribution_model = :model AND model_version = :version AND touchpoint_ts >= :range_start AND touchpoint_ts < :range_end GROUP BY 1, 2).
- Build spend AS (SELECT line_item_id, (served_ts AT TIME ZONE 'UTC')::date AS d, SUM(billed_price_micros_usd) / 1e6 AS spend FROM ad_impression WHERE is_billable AND served_ts >= :range_start AND served_ts < :range_end GROUP BY 1, 2).
- FULL OUTER JOIN on (line_item_id, d), coalescing both key columns in the select list so rows present on only one side keep their identity.
- Compute cpa = spend / NULLIF(conv, 0), roas = value / NULLIF(spend, 0), and is_mature = d < current_date - (win + 2), where win is the click_window_days read off the pinned model and version. On a spend-only day win is null, so is_mature is null rather than TRUE; decide whether to fall back to the model's configured window or to leave the flag unknown, and say which.
- Re-run the same range a week later and diff the CPA column to measure the actual maturation curve rather than assuming the lag.
Follow-up
- Last Tuesday's CPA improved 15% overnight with no change in delivery. What happened, and what would you have shipped to prevent the question?
- The same conversion has credit rows under last_click and under data_driven. Can you sum them? What do you give a client who wants one number?
- model_version changed on the 14th. How do you present a series that spans that boundary without implying a performance change?
What metrics would you use to evaluate a highly imbalanced dataset whe…
What metrics would you use to evaluate a highly imbalanced dataset where only 0.01% of impressions result in a conversion, and why is accuracy a poor choice here?
Approach
- 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.
- Fix the population and the time window before naming any metric.
Follow-up
- What would you do if the primary metric and the guardrail moved in opposite directions?
- How would you detect that the metric is being gamed rather than genuinely improving?
Design a framework and set of metrics to measure and improve customer …
Design a framework and set of metrics to measure and improve customer retention for an advertiser using our platform.
Approach
- Fix the population and the time window before naming any metric.
- Name one primary metric, then the guardrail that stops it being gamed.
- Decompose the metric into the rates that drive it, and say which one you would check first.
Follow-up
- What would you do if the primary metric and the guardrail moved in opposite directions?
- Which segment would you cut first, and what would that rule out?
How would you design an A/B test to evaluate a new bidding algorithm, …
How would you design an A/B test to evaluate a new bidding algorithm, and how would you account for network effects or auction interference between the control and treatment groups?
Approach
- Name the randomisation unit first; it decides the variance and what the test can detect.
- Say whether units interfere with each other, and switch design if they do.
- State the primary metric and the minimum effect worth shipping, then size the test.
Follow-up
- How would you handle interference between treated and control units?
- What would you conclude if the result is positive but the test is underpowered?
Explain the mathematical difference between Bayesian and Frequentist a…
Explain the mathematical difference between Bayesian and Frequentist approaches to A/B testing, and explain when you would choose one over the other.
Approach
- State the primary metric and the minimum effect worth shipping, then size the test.
- Name the randomisation unit first; it decides the variance and what the test can detect.
- Say whether units interfere with each other, and switch design if they do.
Follow-up
- What would you do if you could not randomise at all?
- What would you conclude if the result is positive but the test is underpowered?
Design a performance metric the owning team cannot edit
A bidding team proposes to be evaluated on attributed cost per action: billable spend from ad_impression over SUM(credit_fraction) from attribution_credit, which carries attribution_model, model_version, click_window_days and view_window_days. The same organisation owns the attribution configuration. Deliverable: name three configuration edits that improve this metric with no change to bids, delivery or creative; then propose a primary metric that cannot be improved that way, plus the guardrails you would keep on the attributed number.
Approach
- Write the metric as a formula and audit every input for whether it describes behaviour or policy: CPA = spend / SUM(credit_fraction). Spend is contractual and hard to fake. The denominator is the output of a rules engine, and the rules are owned by the team being measured.
- Name the three levers concretely. Widening
view_window_daysfrom 1 to 7 admits view-through conversions that previously fell outside the window. Wideningclick_window_daysfrom 7 to 28 admits late converters. Switchingattribution_modelfromlast_clicktolinear,position_basedordata_drivenspreads credit onto mid-path touchpoints that previously earned zero. Each raises the denominator with identical bids, identical clearing prices and identical creative. - Show the arithmetic is not conserved across channels: credit sums to 1.0 within one conversion for one model and version, but each channel reports its own credited total, so the sum of attributed conversions across a portfolio routinely exceeds the advertiser's own order count. That is a property of the convention, not a bug, and it is why the number cannot anchor a performance contract.
- Propose the primary whose inputs the team does not own: incremental conversions per $1,000 of spend, estimated from an always-on randomised holdout at a unit that contains the budget — a geographic market or a separately budgeted cell — or from a ghost-bid holdout where control is defined by winning the auction and withholding the creative. Randomising users inside one shared budget does not estimate this, because the arms compete for the same budget and the same auctions.
- Keep attributed CPA, but freeze it: put
attribution_model,model_version,click_window_daysandview_window_daysinto the metric's key so a configuration change creates a new series instead of moving the old one. Add a maturation guardrail — the share of a date's eventual credit that has landed by read time — so nobody reads a fresh week against a matured one.
Worked solution 35 min
- Recompute one month of attributed CPA from unchanged delivery logs under view_window_days of 1 and 7 and record the gap.
- For one conversion with four touchpoints across two channels, hand-split credit under last_click, linear and position_based and note which line item's reported CPA improves under each.
- Specify the holdout: randomisation unit, how budget is separated per arm, the estimator, and the analysis window including the conversion tail.
- Write the frozen-config metric key and the maturation guardrail definition.
- Write the two-sentence argument for why attributed numbers stay as a budget-splitting convention while reallocation decisions are sourced from the holdout.
Follow-up
- The holdout costs roughly 5% of spend in forgone delivery. How do you justify that to the advertiser paying for it?
- Data-driven attribution is fitted rather than hand-set. Does that make it harder to game, or just harder to audit?
- An advertiser demands the wider view window because another platform reports it that way. What do you do?
Attributed conversions vanished for six hours, then partly returned
Attributed conversions for 3 September show a hole between 02:00 and 08:00 UTC. A re-query the next morning shows most of the missing conversion_event rows now present, but attribution_credit for those touchpoint dates is still low and CPA on the dashboard is still wrong. conversion_event has conversion_ts and received_ts; attribution_credit is keyed on (conversion_id, touchpoint_id, attribution_model, model_version) and carries computed_at_ts. Determine what broke, what is still broken, and specify the repair.
Approach
- Count conversion_event by received_ts hour, not by conversion_ts. An ingestion outage is a hole on the arrival clock; on the event clock it looks like a dip that mysteriously heals, which is why the second query disagrees with the first.
- Confirm the backfill on the event clock: for the affected conversion_ts hours, compare the row count at the first query against the count now, and read the received_ts of the recovered rows to pin the recovery window.
- Ask whether anything downstream ran during the hole. Take max(computed_at_ts) per touchpoint date in attribution_credit. A computed_at_ts inside the outage means credit was computed against a partial conversion set and, if the job is not re-run, will never see the recovered rows.
- Distinguish the two failures in the writeup because they have different owners and different fixes: upstream ingestion lost six hours and self-healed, while the downstream attribution run is non-idempotent in effect and needs an explicit re-run for the affected touchpoint dates.
- Re-run attribution for those dates only, then verify the fix moved the affected dates and left neighbouring dates untouched before republishing.
Follow-up
- How would you make the attribution job safe to re-run, and what does it cost to recompute a touchpoint date every day until its window closes?
- What alert would have caught this in under 30 minutes, and on which clock would it be evaluated?
- Which already-published numbers need a restatement note, and what do you say in it?
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.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Build 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.
Practice prompt ↗Practice prompt ↗Practice prompt ↗Worked solution ↗02Joins, 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.
Practice prompt ↗Practice prompt ↗Practice prompt ↗03Window 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.
Practice prompt ↗Practice prompt ↗04The 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.
Practice prompt ↗Practice prompt ↗Worked solution ↗05Write 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.
Practice prompt ↗Practice prompt ↗06One 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.
Practice prompt ↗Practice prompt ↗07Full 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.
Practice prompt ↗Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
Most of the questions in this section reduce to one thing: can you be handed a vague request and come back with something useful? Prepare an example where the ask was underspecified, you chose an interpretation, and you said out loud which interpretation you chose. Describing how you narrowed the question matters more than the technique you eventually used.
Explaining an incrementality interval to a non-technical budget owner
A 20-market geo holdout has closed. Incremental return on ad spend is estimated at 1.4 with a 90% interval of 0.6 to 2.2. The same account's last-click attributed ROAS is 4.1. A vice president with no statistics background has ten minutes and must decide whether to renew a $3M annual budget. Deliverable: the explanation you give verbally plus one slide. You may not use the words confidence interval, p-value, or significance, and you must give a recommendation rather than only a range.
Approach
- The interviewer is probing whether you can carry uncertainty into a decision instead of either hiding it or hiding behind it, so convert the interval into money at the real budget first: $3M at 0.6 to 2.2 is $1.8M to $6.6M of incremental revenue, with $4.2M as the central estimate.
- Get the break-even ROAS from the advertiser's gross margin and mark where it falls inside the range. That single number turns a statistical interval into two decision regions, and it is the only quantity the executive needs to reason about.
- Explain the 4.1 without calling it wrong: it counts every conversion that followed an ad, including the ones that would have happened anyway, while the test counts only the ones that would not have. Both are computed correctly and they answer different questions.
- State what would narrow the range and what it costs, in the same units: roughly four times the markets or four times the flight length to halve the width, against the cost of running longer at current spend.
- Pre-commit to the action at each end of the range so the recommendation is falsifiable: what you do if the true value is near 0.6, and what you do if it is near 2.2.
Follow-up
- The vice president asks whether it is working, yes or no. What is your one-sentence answer?
- Break-even is 1.0 and the range is 0.6 to 2.2. Do you renew at full budget, and what would you change about the flight?
Disclosing an invalid-traffic reclassification that changes invoiced numbers
A measurement vendor reclassifies sophisticated invalid traffic for a six-week period. On re-read, 4.1% of one advertiser's delivered impressions flip from valid to sivt, and their reported CPA rises about 6% across two already-invoiced months. You did not cause it, the contract's make-good language is ambiguous, and a colleague suggests waiting to see whether the advertiser notices. Deliverable: the note you send the advertiser, what you say to finance and the account team beforehand, and the position you take internally on waiting.
Approach
- The interviewer is probing whether you will bound an uncomfortable number before negotiating about it, so recompute the six weeks with the restated ivt_status first, splitting givt from sivt, and produce the impact by campaign and by invoiced month: impressions flipped, billed amount on those exact rows, and the CPA delta.
- Sequence disclosure so one number exists before anyone speaks externally: finance first, because they own the credit and the invoices; the account team second, because they own the relationship; the advertiser third, with the figure both internal parties have already approved.
- Write the note in the restatement shape rather than as an apology: what was reclassified and by whom, which dates, the direction and size, the billing position, and what the advertiser needs to do, which is nothing.
- Take the position on waiting explicitly and in commercial terms, not only ethical ones. The reclassification is visible in the vendor's own dashboard, so waiting converts a correction the advertiser receives from you into a discrepancy they discover themselves, which is the version that costs the renewal.
- Close it structurally: treat invalid-traffic rates as restatable on read with a published reclassification lag, and subscribe to the vendor's reclassification feed so the next event is detected rather than discovered.
Follow-up
- The account team says this disclosure will cost us the renewal. Does that change what you send?
- The contract is silent on reclassification make-goods. How would you price one, and what precedent are you setting?
- What do you do if finance instructs you not to send the note?
Ranking three teams' requests against one sprint of capacity
You have three requests and capacity for roughly one and a half. The supply team wants eligible-RPM impact sizing for a floor-price change shipping in two weeks. An advertiser team wants a pacing diagnosis for a $400k line item delivering at 60% of budget. The invalid-traffic team wants help validating a new sophisticated-invalid-traffic classifier before it restates last quarter. Deliverable: your ranking with the reason for each, exactly what you say to the two teams you deprioritise, and the trigger that would reorder the list.
Approach
- The interviewer is probing whether you rank on decision structure or on social pressure, so attach two attributes to each request before ranking: the date the decision is actually made, and how expensive it is to reverse afterwards. Seniority of the requester is not one of the inputs.
- Look for the request that can be collapsed rather than sequenced. The pacing diagnosis is usually a single grouped query over bid_request_log by no_bid_reason and response_status per hour for that line_item_id; time-boxed to two hours it stops competing for the sprint at all.
- Size the other two in revenue at risk per week so the ranking is arguable rather than asserted: the floor change's expected eligible-RPM delta against the spend it touches, and the invoiced spend inside the period the classifier would restate.
- Rank on irreversibility where deadlines are close together. A classifier that restates already-invoiced periods is the hardest of the three to unwind once shipped, which can outrank a nearer deadline.
- Deprioritise with a date and a fallback rather than an apology: name what you will not do, when you could, the smallest thing you can give them now, and who else could take it.
Follow-up
- The underdelivering advertiser escalates to your director. What actually changes in your plan, and what do you refuse to change?
- How do you stop the pacing question arriving fresh every sprint?
- 01
A 20-market geo holdout has closed. Incremental return on ad spend is estimated at 1.4 with a 90% interval of 0.6 to 2.2. The same account's last-click attributed ROAS is 4.1. A vice president with no statistics background has ten minutes and must decide whether to renew a $3M annual budget. Deliverable: the explanation you give verbally plus one slide. You may not use the words confidence interval, p-value, or significance, and you must give a recommendation rather than only a range.
- 02
A measurement vendor reclassifies sophisticated invalid traffic for a six-week period. On re-read, 4.1% of one advertiser's delivered impressions flip from valid to sivt, and their reported CPA rises about 6% across two already-invoiced months. You did not cause it, the contract's make-good language is ambiguous, and a colleague suggests waiting to see whether the advertiser notices. Deliverable: the note you send the advertiser, what you say to finance and the account team beforehand, and the position you take internally on waiting.
- 03
You have three requests and capacity for roughly one and a half. The supply team wants eligible-RPM impact sizing for a floor-price change shipping in two weeks. An advertiser team wants a pacing diagnosis for a $400k line item delivering at 60% of budget. The invalid-traffic team wants help validating a new sophisticated-invalid-traffic classifier before it restates last quarter. Deliverable: your ranking with the reason for each, exactly what you say to the two teams you deprioritise, and the trigger that would reorder the list.
Is this an official Moloco interview guide?
No. It is PracHub's own research and practice material for the Data Scientist role at Moloco. Rounds and questions reflect what candidates have reported, not a process Moloco has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗How difficult is the technical assessment at Moloco?
The technical bar at Moloco is exceptionally high. Candidates frequently report that Moloco's Online Assessments and technical interviews are highly demanding, particularly regarding probability, statistics, and live coding. Successful preparation requires a deep, first-principles understanding of mathematical concepts rather than superficial memorization.
PracHub interview research ↗What is the typical timeline for the interview process?
The process generally takes between three to six weeks from the initial HR screen to the final offer decision. However, because Moloco's engineering and recruiting teams coordinate across global offices (including Redwood City, San Francisco, Seattle, Seoul, and London), response times can occasionally vary. Stay proactive and maintain open communication with your recruiter.
PracHub interview research ↗How much ad-tech domain knowledge is expected during the interview?
While prior experience in ad-tech (such as real-time bidding or DSP mechanics) is highly valued and will give you a significant advantage, it is not strictly required. Interviewers prioritize strong foundational skills in machine learning, statistics, and problem-solving. If you have a solid technical foundation, you can quickly learn the domain specifics on the job.
PracHub interview research ↗What is the working style and culture like on the Data Science team?
Moloco has a highly collaborative, engineering-driven culture that values technical excellence, speed, and data-driven decision-making. Moloco operates with a flat organizational structure where data scientists are given significant autonomy and ownership over their projects. You will work alongside incredibly smart colleagues who are passionate about solving hard mathematical problems.
PracHub interview research ↗Sources & methodology 3 sources ↗
Official role evidence, timestamped platform data and clearly labeled preparation advice.
- 01PracHub interview research ↗
PracHub editorial research into this company and role, maintained with this guide. Candidate-reported, not an employer publication.
platform · Accessed 2026-09-22 - 02PracHub Data Scientist practice ↗
Cross-company practice questions for this role.
platform · Accessed 2026-09-22 - 03PracHub interview preparation framework ↗
The framework the preparation plan follows.
platform · Accessed 2026-09-22