This guide covers what a Data Scientist at Ubisoft is expected to do and how to prepare for the interview.
Initial Screening
reportedMost candidates lose this call inside the first two minutes, during the walkthrough of their own background. The account runs chronologically, sits at the level of tools and titles, and never arrives at a decision anyone could have disagreed with. Anchor on a problem instead of a timeline: what the team could not answer, what you did about it, what happened next. Ninety seconds is enough, and stopping on time leaves room for the half of the call that belongs to you. What you ask about how work gets prioritised signals your level more reliably than the walkthrough does.
What to demonstrate
- Whether your background summary has a shape (problem, decision, consequence) or is a chronological list of tools and employers
- Whether you can account for gaps, short stints and the reason you are looking, unprompted and without hedging
- The substance of the questions you ask back, which an experienced screener reads as a level signal
How to prepare
- Time your opening walkthrough against a clock. If it runs past two minutes, compress the earliest role into a single clause and spend the recovered time on the most recent one
- Write one honest sentence for every gap or short stint visible on your resume and offer it before being asked about it
- Prepare questions about how work arrives and gets prioritised: who writes the request, how often priorities change, and what happens to an analysis after it is delivered
Technical Discussions
reportedMuch of what gets scored here happens out loud while you type. Nobody can see your reasoning inside a half-written query, so five silent minutes read as being stuck even when they are not. State the plan in plain language first: which tables, what grain you are aggregating to, and the one filter that defines the population. Then write it. The narration doubles as insurance, because a wrong plan gets caught early and cheaply while a wrong query gets caught at the end with no time left to redo it. A timed statistics section, where one exists, is a separate test with its own clock.
What to demonstrate
- Whether the query you write matches the plan you just described
- What you do with a hint, meaning whether the correction gets absorbed or the first approach gets defended
- Whether you can debug your own wrong output by reading the result set and naming which part of the query produced the anomaly
How to prepare
- Solve three problems while screen-sharing into a recording, then watch it back and mark every stretch longer than thirty seconds where you said nothing
- Practise compressing the plan into one sentence before typing, then check afterwards whether the finished query actually matched it
- Time yourself on statistics questions that carry a business reading, such as what a confidence interval does and does not claim, rather than re-reading notes without a clock
Final Rounds
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
PracHub editorial advice for the preparation topics above.
Comparing retention or spend across progression stages reached.
Reaching level 20 requires having survived long enough to reach level 20, so grouping by max level attained conditions on the outcome you are trying to explain. Every correlate of playing longer then looks causal, and 'players who join a guild retain better' is the canonical version of this error. The defensible framing fixes a cohort at a common age or a common exposure point and measures forward from there, or models the hazard with progression as a time-varying covariate.
Reading economy health from average balances.
A faucet change can leave the mean balance flat while the top decile accumulates a stock it has no reason to spend and the median player stays starved, which is the state in which a premium shortcut stops selling. The diagnostic pair is the sink-to-faucet ratio per currency and the full balance percentile curve over time, not the mean. Ledger reversals and customer-service grants must be excluded from both sides or a single large correction will look like a genuine faucet.
Stopping an experiment the moment it crosses significance
Fix the sample size or duration before launch, or use a method built for continuous monitoring such as a sequential test, always-valid confidence intervals, or group-sequential boundaries. Repeatedly checking a fixed-horizon p-value against 0.05 pushes the real false-positive rate well above 5 percent.
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.
Discrete hazard of churn indexed by matches played
Given matches (player_id, started_at) for one install cohort, sessions (player_id, session_start_ts), and a data cutoff timestamp, define churn at match k as no session starting in (t_k, t_k + 14 days], where t_k is the start of that player's k-th match. Build the discrete hazard h(k) and the survival curve S(k) for k = 1 to 50. A player-index enters the risk set at k only if the player has not already churned before k and t_k is at or before cutoff minus 14 days. Return k, risk_set, churned, h and S, and name the two largest hazard spikes.
Approach
- Build the match index yourself: sort matches by (player_id, started_at) and set k = groupby('player_id').cumcount() + 1. Never take a stored index without verifying it is dense per player, because one gap shifts every downstream k for that player.
- Decide churn per (player, k) without a cross join. Sort each player's session timestamps once and use np.searchsorted to ask whether any session falls in the half-open window (t_k, t_k + 14d]. A merge of matches to sessions is quadratic in the busiest players and is where this exercise usually dies.
- Keep observability separate from the event. A player-index is usable at k only when t_k is at or before cutoff minus 14 days; a player whose 30th match was yesterday contributes to k = 1 through 29 and then leaves. This is right-censoring on the match axis, and dropping those players outright instead of truncating them biases the entire curve toward whoever plays fastest.
- Compute h(k) = churned_at_k / risk_set_k and S(k) as the cumulative product of (1 - h(j)) for j up to k. The product form is what makes the censoring handling count; a naive share of the original cohort still alive divides by a denominator containing players who could never have been observed at k.
- Read the spikes against the loop rather than the calendar: a bump at a given k usually maps to a difficulty wall, the end of a tutorial reward track, or the point where a faucet stops paying. Name the k values, then name the query that would confirm each one, rather than asserting a cause.
Follow-up
- Someone reports that players who reach match 50 retain four times better. Rewrite that as a claim you would be willing to defend, and say what it costs you to do so.
- Your definition retires a player who keeps logging in but stops playing matches. Is that churn? What does including or excluding them do to the curve?
- Add a time-varying covariate for whether the player was in a party at match k. What changes about the estimator, and what can you now claim that you could not before?
Three integrity checks over a ledger and purchase join
Given ledger (ledger_id, player_id, occurred_at, currency_code, delta_amount, balance_after, is_reversal, reversed_ledger_id, transaction_id) and txns (transaction_id, player_id, purchase_ts, price_usd_net, status), write check_ledger(ledger, txns) returning violations as rule_code, player_id, ledger_id, detail. Rule 1: balance_after that does not equal the prior balance_after plus delta_amount within (player_id, currency_code) ordered by (occurred_at, ledger_id). Rule 2: is_reversal rows whose reversed_ledger_id is missing, points at a different player or currency, or whose delta_amount is not the exact negative of its target. Rule 3: transaction_ids carried on more than one ledger row. For rule 3 also report completed net revenue computed before and after an inner merge of txns onto ledger.
Approach
- Rule 1 is a shifted comparison inside the sorted group: take balance_after.shift() within (player_id, currency_code) ordered by (occurred_at, ledger_id) and flag where balance_after minus the prior value does not equal delta_amount. Compare the first row of each group against delta_amount alone. Keep the arithmetic in int64; a float cast makes large balances differ at the eighth significant digit and floods the rule with phantom violations.
- Rule 2 is a self-merge of the reversal rows back onto the ledger by reversed_ledger_id. Use a left merge so a dangling pointer survives as a null and becomes a violation. An inner merge deletes precisely the rows you were asked to find.
- Split rule 2 into three codes rather than one: missing target, mismatched player or currency, and a delta that is not the exact negative. Collapsing them throws away the diagnostic that tells you whether this is a bad backfill or a bad writer.
- Rule 3 is ledger.groupby('transaction_id').size() over non-null transaction_ids, kept where the count exceeds one. Then compute completed net revenue two ways: from txns alone, and from the same rows after an inner merge onto ledger. The merged figure is inflated by exactly the fan-out multiplicity, and it is the figure that ends up in a revenue deck when someone joins for an item name.
- Return one long frame with a rule_code column rather than three separate frames. A monitor wants counts per rule per day, and a wide shape makes that awkward to chart and awkward to alert on.
Follow-up
- Rule 1 fires on 0.02 percent of rows, all on one currency, all inside one hour. Incident or backfill, and what distinguishes them in the data?
- How do you run rule 1 incrementally each day without re-reading the full history, given that the check is defined over adjacent rows?
- Rule 3 flags a transaction with three grant rows that turns out to be legitimate. What produces several ledger entries per purchase, and how do you whitelist it without blinding the rule?
Sessionise a raw client event stream with idle timeouts
You have events: player_id, event_ts (UTC), device_id, app_version, event_name. A session is a run of consecutive events from one player on one device with no gap exceeding 1800 seconds; a device change starts a new session regardless of gap. Produce one row per session with session_id, player_id, device_id, session_start_ts, session_end_ts, duration_seconds, event_count, the app_version observed at session start, and ended_clean, true when the final event_name is 'app_background' or 'app_exit'. Sessions may span midnight. Detect boundaries vectorised rather than looping per player.
Approach
- Sort by (player_id, device_id, event_ts), then compute the gap with groupby(['player_id','device_id'])['event_ts'].diff().dt.total_seconds(). The groupby is the whole point: it stops the diff carrying the last event of one player into the first event of the next.
- Flag boundaries as is_new = gap.isna() | (gap > 1800). Strictly greater means a gap of exactly 1800 stays inside the session. Either convention is defensible; write down which one you picked, because it moves the session count by a percent or two and nobody notices until two teams disagree.
- Assign session_id = is_new.cumsum() over the sorted frame. Because the frame is sorted and is_new is true at every group's first row, one global cumsum already produces ids that never span a player or a device, so no per-group loop is needed.
- Aggregate with a single named-agg groupby on session_id: min and max of event_ts, size, first app_version, last event_name. Take first app_version rather than mode, since an in-session update is a real event you want visible, not averaged away.
- Compute duration as (session_end_ts - session_start_ts).total_seconds() and state the caveat rather than hiding it: this is the span between observed events, so a crashed client understates the true session by the unobserved tail. ended_clean is what carries that information; do not patch the duration by adding the timeout.
Worked solution 30 min
- Sort and compute the grouped gap, then verify the gap is NaN at exactly one row per (player_id, device_id).
- Build is_new and session_id by cumsum, then assert no session_id maps to more than one player_id or device_id.
- Aggregate to session grain with one named-agg groupby and derive duration_seconds and ended_clean from the aggregated columns.
- Validate the maximum within-session gap by re-deriving gaps inside each session and confirming none exceeds the timeout.
Follow-up
- Mobile clients buffer events and upload them late and out of order. What breaks in this job, and how do you make a daily re-run idempotent?
- A player has two devices live at once, so your definition yields two overlapping sessions. Is that the right answer for a concurrency metric, and is it the right answer for sessions per active day?
- What happens to the session count and to mean duration if the idle timeout drops from 30 minutes to 5?
How do you use SQL window functions to calculate rolling averages or i…
How do you use SQL window functions to calculate rolling averages or identify user session sequences?
Approach
- Handle the rows that do not match: a LEFT JOIN with a NULL check is usually the question.
- Check whether any join is one-to-many before aggregating, or the sums inflate.
- State the window function and its partition and ordering out loud before writing it.
Follow-up
- How would you verify this result without re-running the same query?
- How does the query change if the join becomes one-to-many?
Write a query to identify the top 5% of players based on their spend o…
Write a query to identify the top 5% of players based on their spend over a specific timeframe.
Approach
- Handle the rows that do not match: a LEFT JOIN with a NULL check is usually the question.
- Check whether any join is one-to-many before aggregating, or the sums inflate.
- Say which table is the grain you start from, and join outward from 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?
Stitch crash-fragmented sessions into play blocks with gaps and islands
fct_session has session_id, player_id, session_start_ts, session_end_ts (NULL when the client crashed), duration_seconds (NULL alongside it), platform and ended_reason. A crash splits one sitting into two rows seconds apart. Group consecutive sessions for the same player and platform into play blocks, starting a new block when the gap from the previous end to the next start is 120 seconds or more. For NULL ends, impute session_start_ts plus that player's median duration_seconds on the same platform. Return one row per block.
Approach
- Build the imputed end in its own CTE: COALESCE(session_end_ts, session_start_ts + the median duration as an interval). Compute the median with PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY duration_seconds) grouped by player_id and platform over the non-NULL rows, then join it back. Players with no non-NULL session have no median, so decide explicitly whether to drop them and report how many.
- Get the previous end with LAG(imputed_end) OVER (PARTITION BY player_id, platform ORDER BY session_start_ts, session_id). The session_id tiebreak matters because two sessions can share a start timestamp on a device that relaunched instantly.
- Flag a block boundary where prev_end IS NULL (the first session in the partition) OR session_start_ts - prev_end >= interval '120 seconds', and cast that flag to an integer.
- Turn flags into block ids with SUM(flag) OVER (PARTITION BY player_id, platform ORDER BY session_start_ts, session_id ROWS UNBOUNDED PRECEDING). This is the gaps-and-islands step. The ROWS frame here is explicit, not load-bearing: session_id is unique, so every row is its own peer group and the default RANGE frame would produce the same block ids. What actually keeps two sittings apart is the session_id tiebreak, which is why it has to appear in all three window clauses.
- Drop that tiebreak and the ordering stops being total, which breaks the step in one of two ways depending on the frame. Under the default RANGE, sessions sharing a start timestamp are peers, so each receives the tie group's closing cumulative total, they share a block id, and two genuinely separate sittings merge. Under ROWS they get distinct ids, but the order inside the tie is unspecified, so which session opens the block, and the LAG that fed the boundary flag, can differ between runs on identical data.
- Aggregate by (player_id, platform, block_id) for MIN(session_start_ts), MAX(imputed_end), COUNT(*), the summed in-session seconds and BOOL_OR(ended_reason = 'crash') to mark blocks stitched across a crash.
- State the direction of the imputation bias: if the imputed end lands earlier than the true end, the gap is overstated and the query under-stitches, so the crash-stitch rate under this rule is a lower bound.
Worked solution 35 min
- Compute per-(player_id, platform) median duration_seconds over rows where it is NOT NULL, and count how many players have no median at all.
- Build the imputed_end CTE and confirm no NULL ends remain among the rows you kept.
- Add LAG, the boundary flag and the cumulative-sum block id in one CTE, all three carrying the same PARTITION BY (player_id, platform) and the same ORDER BY (session_start_ts, session_id). If the three orderings disagree, the flags and the ids are computed over different sequences and the block boundaries land on the wrong rows.
- Aggregate to block grain and spot-check a player known to have crashed: their two adjacent rows should share one block id.
- Recompute with the threshold at 30 and at 600 seconds and watch the block count move. A threshold that changes nothing means crashes are not in fact producing adjacent rows.
Follow-up
- The 120-second threshold is an assertion. How would you pick it from the distribution of inter-session gaps instead?
- Under your imputation, does the block count over-count or under-count real sittings, and which direction would you rather err in for a crash-impact readout?
- A player is on two platforms at once. Should that be one block or two, and what does your partition key currently decide for you?
How do you balance monetization metrics with player satisfaction and l…
How do you balance monetization metrics with player satisfaction and long-term retention?
Approach
- Decompose the metric into the rates that drive it, and say which one you would check first.
- Name one primary metric, then the guardrail that stops it being gamed.
- 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?
- Which segment would you cut first, and what would that rule out?
If a core engagement metric suddenly drops by 10% overnight, how would…
If a core engagement metric suddenly drops by 10% overnight, how would you investigate the root cause?
Approach
- Name one primary metric, then the guardrail that stops it being gamed.
- Fix the population and the time window before naming any metric.
- State what result would change your recommendation, so the answer is falsifiable.
Follow-up
- How would you detect that the metric is being gamed rather than genuinely improving?
- Which segment would you cut first, and what would that rule out?
How would you design a metric to measure the success of a new in-game …
How would you design a metric to measure the success of a new in-game event?
Approach
- Restate the decision this analysis has to support, and who acts on the answer.
- Name one primary metric, then the guardrail that stops it being gamed.
- 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?
- Which segment would you cut first, and what would that rule out?
What motivates you to work in the gaming industry, and how do you stay…
What motivates you to work in the gaming industry, and how do you stay updated with current trends?
Approach
- Decompose the metric into the rates that drive it, and say which one you would check first.
- Name one primary metric, then the guardrail that stops it being gamed.
- Restate the decision this analysis has to support, and who acts on the answer.
Follow-up
- What would you do if the primary metric and the guardrail moved in opposite directions?
- Which segment would you cut first, and what would that rule out?
How do you ensure statistical significance when your sample size is li…
How do you ensure statistical significance when your sample size is limited?
Approach
- Name the randomisation unit first; it decides the variance and what the test can detect.
- Decide the analysis before seeing data, including how long it runs and when you look.
- 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?
What are the most common experimentation pitfalls when testing feature…
What are the most common experimentation pitfalls when testing features in a live game?
Approach
- 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.
- Name the guardrails that would stop a launch even on a positive primary result.
Follow-up
- What would you conclude if the result is positive but the test is underpowered?
- What would you do if you could not randomise at all?
Define success for a redesigned daily login reward
The team is replacing a flat daily login grant with an escalating seven-day streak that pays soft currency through fct_currency_ledger (flow_type='faucet', source_system='daily_login'). A product manager proposes daily active users as the success metric. You have dim_player, fct_session, fct_match_participant and fct_currency_ledger. Produce a metric tree from the grant to a business outcome, name one primary metric and two guardrails, and explain in writing why daily active users can be moved by this change without the game getting better.
Approach
- Separate the mechanism from the outcome. The reward is a faucet, so its first-order effects are an app open and an increase in soft-currency inflow, and neither is value. Write the tree as grant, app open, session, match started, match reaching a terminal result, retained the following week, currency spent on a sink.
- Reject daily active users explicitly, because a notification-driven collect-and-close satisfies it. A login reward pays for exactly that behaviour, and it shows up as fct_session rows with matches_started = 0.
- Pick daily core-loop players as primary: distinct non-test players per UTC day with at least one fct_match_participant row whose result is not 'abandon'. Collecting a reward and closing the app does not count toward it.
- Guardrail one is the sink-to-faucet ratio for currency_code = 'soft' over a trailing 7 days, excluding is_reversal = true and flow_type in ('transfer','correction'). A large new faucet pushes the ratio below 1, balances accumulate, and every soft-priced sink loses its bite.
- Guardrail two is the share of sessions with matches_started = 0, cut by platform. This is the direct measurement of the behaviour that daily active users hides.
- State the decision rule before launch: ship only if core-loop players rise while the soft sink-to-faucet ratio stays above its pre-launch level minus a pre-registered margin.
Worked solution 20 min
- Write the tree on one line from faucet grant to retained core-loop player, marking which node each candidate metric sits on.
- Draft the primary metric definition in full, including the is_test_account = false filter and the exclusion of result = 'abandon'.
- Draft both guardrails with exact windows and exclusions: the 7-day soft sink-to-faucet ratio with reversals, transfers and corrections dropped, and the zero-match session share by platform.
- Write the one-paragraph gameability argument, whose core is that the cheapest route to more daily active users under this change is a push notification and a collect button.
- State the pre-registered ship rule and the rollback threshold.
Follow-up
- The streak resets at UTC midnight. What does that do to your day boundary for players whose evening sits either side of it, and which metric shows the damage first?
- Soft-currency inflow rises 18% and the sink-to-faucet ratio falls from 1.05 to 0.86 while core-loop players are up 2%. What do you recommend?
- How would you detect accounts that farm the streak and never enter a match?
Revenue per player-day rose while every channel got worse
Net revenue per active player-day over a trailing 28 days rose from $0.1440 to $0.1656, up 15%, while net revenue per active player-day fell about 4% inside every acquisition_channel. Organic share of player-days went from 60% to 75% as a cross-promotion campaign ended. Using dim_player (player_id, acquisition_channel), fct_session (player_id, session_start_ts) and fct_iap_transaction (player_id, purchase_ts, price_usd_net, status), decompose the rise and state whether monetisation improved.
Approach
- Rebuild the metric from segment parts and confirm the rebuild before decomposing. For each channel and period, compute player-days, net revenue and their ratio, then check the player-day-weighted channel ratios reproduce both reported overall figures. A decomposition on a table that does not rebuild the headline attributes arithmetic error to mix.
- Apply the two-term split with the weighting stated. Mix effect is the sum over channels of (share_after minus share_before) times ratio_before; within-segment effect is the sum over channels of share_after times (ratio_after minus ratio_before). Name the pairing, because the alternative assigns the interaction term to the other side.
- Recognise that the denominator moved for a reason unrelated to monetisation. A cross-promotion campaign ending removes player-days that carried near-zero revenue, so the blended average rises mechanically while nothing about how players monetise improved. Check the absolute player-day counts, not only the shares, to confirm the shift came from the low-monetising channel shrinking rather than the high-monetising one growing.
- Run the guardrails attached to this metric rather than stopping at the decomposition. Compute the share of net revenue contributed by the top 1% of payers, and D28 retention of players who have never completed a purchase. A rise in the first alongside a fall in the second means the within-segment decline is a real deterioration of the free experience, not noise.
- Decompose the within-segment 4% one level further into paying conversion per player-day and net spend per converting player-day, so the recommendation points at a mechanism. These are different problems with different owners.
- State the verdict without hedging: total net revenue and the per-channel ratios are the numbers that answer whether monetisation improved, and both say it did not. The blended metric rose because its denominator shrank where revenue was thinnest.
Follow-up
- Total net revenue in absolute dollars is the obvious cross-check. What does it do here, and why is it not sufficient on its own?
- The cross-promotion channel was unprofitable per player-day. Does ending it make this metric a better or a worse measure of the business?
- What would you put in place so that a denominator change of this size cannot be reported as a monetisation win again?
Four days spend equal time on query work, statistics, modelling and product judgement at deliberately shallow depth, which produces a scored map of where you actually stand. The last three days spend everything on the two areas the role weights most, and close by re-running day one to measure movement.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Breadth pass: query fluency
- Solve six prompts spanning aggregation, joins, window functions and date arithmetic in 60 minutes total, stopping at 10 minutes each whether or not it works, and mark every prompt as solved, solved slowly, or stuck.
- For each unsolved prompt write the single blocking sentence (I lost the grain, I did not know the frame clause, I could not express the date boundary) instead of reading the solution.
- Translate one pandas transformation you know well into SQL and one SQL query into pandas, checking that both return the same row count and the same totals.
Deliverable: A scored six-row table, one line per prompt, saved for the day-seven re-run.
Practice prompt ↗Practice prompt ↗Practice prompt ↗Worked solution ↗02Breadth pass: statistics and inference
- Answer ten short questions in writing with nothing open: what a p-value is conditional on, what a 95 percent interval covers across repeated samples, when a paired test is the right one, what the bootstrap estimates, why multiple comparisons inflate false positives, how controlling the family-wise error rate differs from controlling the false discovery rate, what power depends on, what a missed real effect costs a product, the three situations where the central limit theorem does not rescue you (small n, very heavy tails, dependent observations), and what a standard error is the standard deviation of.
- Grade yourself against a reference and count only the answers that were exactly right, not the ones that were nearly right.
- Rewrite the two weakest answers the following morning from memory in full sentences.
Deliverable: Ten graded answers with an honest count of exact hits.
Practice prompt ↗Practice prompt ↗Practice prompt ↗03Breadth pass: modelling
- Take one tabular dataset end to end in 90 minutes: a leakage-safe split, a baseline that is not a model (majority class or historical mean), one regularized linear model, one gradient-boosted tree, and a single evaluation metric chosen before you look at any result.
- Write why that metric fits the cost structure: precision at a fixed recall for alerting, calibration for anything feeding a price or a threshold, ranking metrics for retrieval, and note that area under the ROC curve is insensitive to class balance in a way that can flatter a rare-positive problem.
- Name the leak you were most likely to introduce (an encoding fit on all rows before splitting, or a feature computed after the label's timestamp) and write the check that would have caught it.
Deliverable: A notebook whose first cell states the metric and the baseline, plus two lines on what beat what and by how much.
Practice prompt ↗Practice prompt ↗Practice prompt ↗04Breadth pass: product judgement
- Answer three case prompts aloud at 15 minutes each, timing how long passes before you state a success metric.
- For one case write the first segmentation you would run and the row counts you expect per segment, so that a tiny segment cannot quietly drive the conclusion.
- Take a metric definition you did not write, from a public dashboard, a textbook, or documentation you already have open, and list every place two analysts implementing it would diverge: which rows the denominator admits, whether the unit is an account or a person, what the time window is anchored to, and what happens to data that arrives late. Then write the one question that would close the largest of those gaps.
Deliverable: Three recorded case answers plus an ambiguity list for a metric someone else defined, ending in the single question you would ask about it.
Practice prompt ↗Practice prompt ↗Worked solution ↗05Depth, first area
- Rank the four areas by how many bullet points in the role description each one covers, pick the top one, and spend the entire day inside it.
- Work the six hardest problems you can find in that area and for each write the generalizable move you should have reached for first, rather than the answer.
- Re-solve the two you failed the same evening with notes closed.
Deliverable: Six generalizable moves written as instructions to yourself, not as solutions.
Practice prompt ↗Practice prompt ↗06Depth, second area, and the seam between them
- Repeat the depth protocol on the second-ranked area with the same six-problem structure.
- Construct one problem that requires both areas at once, for example a metric redefinition whose effect you must validate with a test whose readout you then have to query.
- Solve your own combined problem end to end and note where the handoff between the two areas cost you time.
Deliverable: One combined problem, solved end to end, with the handoff failure written down.
Practice prompt ↗Practice prompt ↗07Integration and re-measurement
- Re-run the six prompts from day one under the same clock and compare both correctness and time.
- Run a 60-minute mixed mock that moves between areas without warning, since switching cost is what breadth passes do not train.
- Write the two areas you would still fail on, and the sentence you will use in the interview when you hit one of them.
Deliverable: A before-and-after score table plus a written plan for the two remaining gaps.
Practice prompt ↗Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
Nearly every data role forces a trade between the analysis you want and the one that fits the decision window. Prepare a case where you deliberately shipped something less rigorous, named the weakness to the person relying on it, and said what would change your answer. The naming is the part interviewers listen for.
Tell me about a time you worked with a team that had conflicting prior…
Tell me about a time you worked with a team that had conflicting priorities; how did you align them?
Approach
- Pick a story where you drove the decision, not one where you observed it.
- State the situation in two sentences and spend the rest on your reasoning.
- Quantify the outcome, including what you would not claim credit for.
Follow-up
- What would you do differently if you ran that project again?
- How did you know the outcome was caused by your change?
Retract a shipped retention claim built on survivorship
Three months ago you published a memo showing that players who join a guild in their first week have 2.1x the D28 retention of those who do not. A feature team has since built a guild-invite prompt on the strength of it, shipping in nine days. You now see that guild status was measured at any point during the first week, among players who were still active at day 7, so the comparison conditions on the outcome it claims to explain. Write the retraction and describe the conversation you have with the feature lead first.
Approach
- Get the corrected number before you send anything. Re-anchor: restrict to players with at least one session in the day-7 elapsed-hour bracket, fix guild status as of that anchor timestamp, and measure D28 forward from it. The corrected effect is usually positive and much smaller, and 'smaller' is a very different memo from 'wrong'.
- Talk to the feature lead privately, first, with the corrected estimate in hand. A retraction that reaches them as a surprise in a group channel costs you the relationship you need for the next correction.
- Structure the memo in decision order: what the claim was, what was mechanically wrong in one plain sentence, what the corrected estimate and interval are, and what decision changes as a result.
- Keep 'the number was wrong' separate from 'the feature is wrong'. Selection bias means the estimate is uninterpretable, not that guilds do nothing. Propose the design that would actually answer it: randomise the invite prompt and measure from assignment, which also gives the team a readout they can defend.
- Offer the hazard framing as the durable fix. Model churn with guild membership as a time-varying covariate so joining is not allowed to predict the survival it required.
- Add one process change, such as an anchor-point check on any adoption-versus-retention claim, so the retraction buys something beyond an apology.
Follow-up
- The corrected lift is 1.3x with an interval of 1.05x to 1.6x. Does the feature still ship in nine days?
- How would you design the randomised version so the prompt itself, rather than guild membership, is what gets randomised?
- What would you have to see in the hazard curve for you to believe the mechanism is real rather than another selection artefact?
State your own impact on a number you did not own
You are writing a one-page performance case that a committee with no context will read. Over two quarters, net revenue per active player-day rose 11%. In that period you designed the pricing experiment behind one shipped change, found and fixed a ledger bug that had been double-counting a faucet in reporting, and argued a team out of a store redesign that a later test showed would have been flat. Write the impact claim you can defend, including the part of the 11% you will not take credit for.
Approach
- Refuse the aggregate as your claim, explicitly and early. The 11% contains seasonality, the release calendar, acquisition mix shift and other teams' shipped work. Claiming it invites exactly one question that then collapses the entire page.
- Claim only estimates that carry their own counterfactual. The pricing experiment has a randomised estimate, so quote the interval rather than the point, and quote the effect on net revenue per active player-day so it is on the same scale as the 11% without being confused for it.
- Price the ledger bug by what it corrupted rather than by revenue, because a reporting fix creates no revenue. Say how far the sink-to-faucet ratio was wrong, for how many weeks, and name the specific decision that was being made on the wrong number.
- Handle the prevented work as the weakest of the three claims and label it as such. State the engineering weeks not spent and cite the later test as the vindication, while being explicit that an avoided cost rests on a counterfactual and is worth less as evidence than a measured lift.
- Include one thing you got wrong in the same period, with what you changed. A page with no misses reads as unaudited, and a committee that finds the miss themselves discounts everything else.
- Make every claim checkable in under a minute: experiment name, date range, readout document, and the person who can confirm it.
Follow-up
- A reviewer says the pricing lift would have happened anyway because a competitor changed prices that quarter. How do you respond?
- How would you have measured the ledger bug's impact if no decision had yet been made on the wrong number?
- What is the strongest evidence you could have collected at the time that you did not?
- 01
Tell me about a time you worked with a team that had conflicting priorities; how did you align them?
- 02
Three months ago you published a memo showing that players who join a guild in their first week have 2.1x the D28 retention of those who do not. A feature team has since built a guild-invite prompt on the strength of it, shipping in nine days. You now see that guild status was measured at any point during the first week, among players who were still active at day 7, so the comparison conditions on the outcome it claims to explain. Write the retraction and describe the conversation you have with the feature lead first.
- 03
You are writing a one-page performance case that a committee with no context will read. Over two quarters, net revenue per active player-day rose 11%. In that period you designed the pricing experiment behind one shipped change, found and fixed a ledger bug that had been double-counting a faucet in reporting, and argued a team out of a store redesign that a later test showed would have been flat. Write the impact claim you can defend, including the part of the 11% you will not take credit for.
Is this an official Ubisoft interview guide?
No. It is PracHub's own research and practice material for the Data Scientist role at Ubisoft. Rounds and questions reflect what candidates have reported, not a process Ubisoft has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗How difficult are the technical interviews?
A: The difficulty is generally balanced. You should expect to be tested on your ability to apply your knowledge to practical scenarios rather than just answering theoretical questions.
PracHub interview research ↗How much time should I dedicate to preparation?
A: Dedicate at least two to three weeks to review your statistics fundamentals and practice SQL queries, especially those involving window functions.
PracHub interview research ↗What is the company culture like?
A: Ubisoft values innovation, passion, and collaborative spirit. Candidates who show a genuine interest in the specific game titles or genres the team works on tend to stand out.
PracHub interview research ↗How hard is the Ubisoft interview?
Candidates most commonly rate Ubisoft interviews as medium, based on 500 reported interviews. About 45% of candidates who interview go on to receive an offer.
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