cswork / libraryOpen practice workspace

Interview guides · Verse

Verse Data Scientist Interview Questions & Guide 2026

Data scientist prep for Verse candidates: drill A/B design, power and minimum detectable effect on original exercises.

The 60-second brief

As a Data Scientist at Verse, you are at the intersection of high-stakes energy markets and cutting-edge machine learning. Your work directly powers Aria, the company’s flagship Energy Cost Intelligence platform, which is designed to help the world’s largest energy buyers navigate extreme market volatility. You are not just building models; you are architecting the financial and operational intelligence that replaces legacy spreadsheets and manual consulting with precision, real-time decision-making.

Most of the loop measures decision-making under uncertainty rather than recall. You are scored on whether you state your assumptions, commit to an estimate you can defend, and say explicitly what evidence would change it.

Focus areas

  • Measure churn only on renewal-eligible accounts
  • Read NRR on a fixed account cohort
  • Separate contracted seats from actively used seats

Overview

The role is inherently cross-functional and highly technical. You will partner with energy buyers, product managers, and software engineers to translate complex business problems—such as electricity market price forecasting, renewable procurement optimization, and solar production anomaly detection—into scalable, production-grade solutions. Whether you are automating power portfolio management or quantifying uncertainty in energy projects, your contributions will directly influence how organizations reduce risk and lower costs in a rapidly electrifying world.

This position is ideal for a practitioner who thrives on autonomy and enjoys the full lifecycle of data science. You will own projects from initial scoping and statistical modeling to cloud-based production deployment and MLOps. If you are passionate about applying rigorous data science to climate tech and want to build tools that have a tangible impact on global sustainability, offers a high-impact environment where your work moves from the whiteboard to the grid in record time.

The Verse interview process

Verse candidates report 3 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.

Initial Screen

A screening call is a matching exercise run by someone who will not evaluate your statistics. They are checking that the work described on your resume is work you personally did, and that its scope matches the level the role is written for. Logistics get settled in the same half hour so nobody spends an interviewer's afternoon on a mismatch. The answer that fails is the one narrated in the plural. If every sentence is 'we built' and 'the team decided', there is nothing specific to write down about you. Name the piece that was yours, the decision you made inside it, and what changed after.

What to demonstrate

  • Whether the ownership implied by your resume survives one round of follow-up about who actually did which part
  • Whether your described scope (data size, stakeholders, what shipped) matches the seniority the role is written at
  • Whether timeline, location and compensation expectations make the rest of the loop worth scheduling

How to prepare

  • Rewrite your top three resume bullets in the first person singular, each with the decision you made and what moved afterwards, then say them out loud once so the 'we' does not return under pressure
  • Attach one number to each project: the baseline, the change, and the window it was measured over. Where impact was never measured, say that plainly rather than inventing a figure
  • Settle your compensation range before the call and give it as a range with a reason behind it, such as current total comp or a competing timeline, instead of deflecting the question twice

Deep-Dive Technical Rounds

Before 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

Project Independence Assessment

Much of what gets scored here happens out loud while you type. Nobody can see your reasoning inside a half-written query, so five silent minutes read as being stuck even when they are not. State the plan in plain language first: which tables, what grain you are aggregating to, and the one filter that defines the population. Then write it. The narration doubles as insurance, because a wrong plan gets caught early and cheaply while a wrong query gets caught at the end with no time left to redo it. A timed statistics section, where one exists, is a separate test with its own clock.

What to demonstrate

  • Whether the query you write matches the plan you just described
  • What you do with a hint, meaning whether the correction gets absorbed or the first approach gets defended
  • Whether you can debug your own wrong output by reading the result set and naming which part of the query produced the anomaly

How to prepare

  • Solve three problems while screen-sharing into a recording, then watch it back and mark every stretch longer than thirty seconds where you said nothing
  • Practise compressing the plan into one sentence before typing, then check afterwards whether the finished query actually matched it
  • Time yourself on statistics questions that carry a business reading, such as what a confidence interval does and does not claim, rather than re-reading notes without a clock

Where candidates lose points

Comparing accounts that received a sales or customer-success touch against those that did not

Assignment of coverage is deliberate and pulls in both directions at once: the largest accounts get a named owner because they are valuable, and the accounts showing distress get one because they are at risk. The comparison therefore mixes a strong positive selection with a strong negative one, and the naive estimate can come out with either sign depending on which assignment rule dominated during the period examined. Nothing about matching on observed size fixes this, because the risk signal that triggered coverage is usually the same signal that predicts the outcome. It needs either an actual randomised or staggered rollout of coverage, or a design built on a capacity constraint or territory boundary that assigns coverage for reasons unrelated to account health.

Reading consumption metrics before the metering lag window has closed

Usage pipelines land late and correct themselves, which is exactly what is_restated and restated_at record. A dashboard queried on day T sees a partially populated tail for the last several days, so the most recent points always slope downward and always look like a regression. Analysts then explain the artefact, and sometimes ship a change to fix it. Establish the empirical settling time by measuring how much a given usage_date's total moves between first_written_at and its final value, exclude that many trailing days from every reportable figure, and never compare a fresh period against a settled one.

Dropping rows with missing values without naming the mechanism

Say whether the values are missing at random, missing by a known process, or missing in a way that depends on the outcome, and handle them accordingly. Deleting incomplete rows silently redefines the population whenever missingness correlates with what you are measuring.

Generalising beyond the population the sample actually supports

State the frame the sample was drawn from and where it diverges from the population you want to talk about: time window, platform, geography, opt-in. If a group is excluded from the frame, either weight to known margins or narrow the claim rather than quietly extending it.

Questions & practice

How do you optimize a resource-heavy model to run efficiently in a cloud environment like AWS or GCP?

Medium · machine learning and modelling

Approach

  1. Say how the offline result would be validated online before it is trusted.
  2. Set a baseline first, so any model has something honest to beat.
  3. Frame the prediction: the label, the moment of prediction, and the action it triggers.

Follow-up

  • Where could label leakage enter this setup?
  • How would you choose the decision threshold, and who owns that choice?

What statistical methods would you use to quantify the uncertainty of a long-term energy project’s financial performance?

Medium · machine learning and modelling

Approach

  1. Set a baseline first, so any model has something honest to beat.
  2. Say how the offline result would be validated online before it is trusted.
  3. Pick an evaluation metric that matches the cost of each error type, not a default.

Follow-up

  • Where could label leakage enter this setup?
  • How would you choose the decision threshold, and who owns that choice?

Describe how you would build an anomaly detection system for solar production data.

Medium · machine learning and modelling

Approach

  1. Pick an evaluation metric that matches the cost of each error type, not a default.
  2. Frame the prediction: the label, the moment of prediction, and the action it triggers.
  3. Check what information would not exist at prediction time, and exclude it.

Follow-up

  • What would you monitor after launch to know the model is still valid?
  • How would you choose the decision threshold, and who owns that choice?

How do you ensure your Python code is maintainable and testable when integrating a model into a production pipeline?

Medium · machine learning and modelling

Approach

  1. Check what information would not exist at prediction time, and exclude it.
  2. Frame the prediction: the label, the moment of prediction, and the action it triggers.
  3. Set a baseline first, so any model has something honest to beat.

Follow-up

  • What would you monitor after launch to know the model is still valid?
  • Where could label leakage enter this setup?

Join daily usage to the contract version live that day

Medium · as-of-join · pandas · numpy · contracts

fct_usage_daily has account_id, usage_date and net_amount_cents. fct_subscription_period has account_id, subscription_period_id, plan_tier, term_start_date, term_end_date, arr_cents and is_current, with one row per contract term version and every amendment inserting a new row. Attach to each usage row the subscription_period_id whose term brackets usage_date (term_start_date <= usage_date <= term_end_date). pd.merge_asof and an interval-condition merge are unavailable; use sorting and numpy.searchsorted. Then report monthly net revenue by plan_tier. Terms for one account do not overlap, and usage may fall outside every term.

Approach

  1. Say out loud why the cheap version is wrong: joining on is_current stamps today's plan tier onto last year's usage, so every account that upgraded has its history reclassified and revenue-by-tier becomes a function of when the query ran.
  2. Sort each account's terms by term_start_date and use np.searchsorted(term_start, usage_date, side='right') - 1 to get the last term that started on or before the usage date. Do it per account group, or globally after encoding (account_id, date) into one monotone key.
  3. searchsorted only enforces the left edge. Validate the right edge afterwards — usage_date <= the candidate's term_end_date — and set the match to NA where it fails. That NA is usage in a gap between contracts and must stay visible instead of being folded into the expired term.
  4. Assert non-overlap before trusting the lookup, and write the assertion so it is capable of passing. prev_end = terms.groupby('account_id').term_end_date.shift() is NaT on each account's first row, and NaT < Timestamp evaluates to False rather than NA, so a comparison followed by .fillna(True) has nothing left to fill and the assertion fires on every account's first term whatever the data looks like. Guard the null yourself: assert (prev_end.isna() | (prev_end < terms.term_start_date)).all(). The failure mode of getting this wrong is not a false alarm you notice once — it is an assertion someone deletes because it never passes, after which overlapping terms make searchsorted return one of them with no trace in the output.
  5. Aggregate after the join, grouping by (usage_date month, plan_tier) with dropna=False so the unmatched bucket appears as its own row and the total still ties to the ungrouped sum of net_amount_cents.

Worked solution (35 min)

  1. terms = terms.sort_values(['account_id','term_start_date']); prev_end = terms.groupby('account_id').term_end_date.shift(); assert (prev_end.isna() | (prev_end < terms.term_start_date)).all()
  2. Per account group: idx = np.searchsorted(g.term_start_date.values, u.usage_date.values, side='right') - 1; rows with idx < 0 are unmatched.
  3. Gather subscription_period_id, plan_tier and term_end_date by positional index, then null the match wherever usage_date > the gathered term_end_date.
  4. monthly = joined.assign(month=joined.usage_date.dt.to_period('M')).groupby(['month','plan_tier'], dropna=False).net_amount_cents.sum()

Expected result: Exactly one output row per input usage row, a subscription_period_id that is NA precisely for usage outside every term, and a monthly table whose values sum to the ungrouped total of net_amount_cents once the NA-tier bucket is included.

Verify it yourself

  • len(joined) == len(usage); the join must not fan out.
  • joined.net_amount_cents.sum() == usage.net_amount_cents.sum().
  • For every matched row, assert term_start_date <= usage_date <= term_end_date directly rather than trusting searchsorted.
  • Run the non-overlap assertion on a frame where every account has exactly one term: prev_end is NaT throughout, so the assertion must pass. An assertion that fires there is testing the shift, not the data.
  • Spot-check one account that upgraded mid-term: its usage before the amendment must still carry the old plan_tier.

Follow-up

  • An amendment takes effect on the 17th of a month. How do you report that month's revenue by tier?
  • What changes if terms can overlap because of a co-term amendment?
  • How would you verify this against a SQL implementation using a BETWEEN condition?

Running commitment burn-down and the date consumption crosses it

Medium · window functions · running totals · committed consumption

fct_subscription_period gives committed_amount_cents, term_start_date and term_end_date for each account's current version where pricing_model = 'committed_consumption'. fct_usage_daily gives account_id, workspace_id, sku_code, usage_date and net_amount_cents, at one row per workspace and SKU per day. Inside each account's current term, return the running total of net_amount_cents by usage_date, the first usage_date on which that running total reaches committed_amount_cents, and the fraction of the term elapsed at that point. Accounts that have not reached their commitment must still appear, with a null crossing date.

Approach

  1. Collapse usage to one row per (account_id, usage_date) first. The fact is grained by workspace and SKU, so a raw running total leaves several rows per date and the first-crossing date becomes dependent on the arbitrary order of rows inside that day.
  2. Restrict to the term with usage_date BETWEEN term_start_date AND term_end_date on the account's current row, so no prior term's consumption leaks into this term's burn-down.
  3. Compute SUM(net_cents) OVER (PARTITION BY account_id ORDER BY usage_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW). Name ROWS explicitly: the default frame is RANGE, which includes all peer rows at the same ORDER BY value and would hand back the whole day's total on each of that day's rows.
  4. Extract the crossing with MIN(usage_date) FILTER (WHERE running_cents >= committed_amount_cents) grouped per account, which returns NULL for accounts still under commitment instead of dropping them.
  5. Elapsed fraction is (crossing_date - term_start_date)::numeric / NULLIF(term_end_date - term_start_date, 0); Postgres date subtraction yields whole days, so the guard matters for same-day terms.
  6. Exclude the trailing days still inside the metering settling window, measured from first_written_at against restated_at, and say how many days you cut and why.

Worked solution (30 min)

  1. Build terms: SELECT account_id, committed_amount_cents, term_start_date, term_end_date FROM fct_subscription_period WHERE is_current AND pricing_model = 'committed_consumption' AND committed_amount_cents IS NOT NULL.
  2. Build daily: join fct_usage_daily to terms on account_id with usage_date inside the term, then GROUP BY account_id, usage_date summing net_amount_cents.
  3. Add running_cents with SUM(...) OVER (PARTITION BY account_id ORDER BY usage_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW).
  4. Aggregate per account with MIN(usage_date) FILTER (WHERE running_cents >= committed_amount_cents) AS crossing_date, then compute the elapsed fraction with the NULLIF guard.
  5. LEFT JOIN that summary back onto terms so every committed account appears, and verify the last running_cents per account equals a plain SUM over the term.

Expected result: A per-account-per-day series of running_cents inside the current term, plus one summary row per committed account carrying crossing_date and term_elapsed_fraction, with crossing_date NULL exactly for accounts whose final running total is below committed_amount_cents.

Verify it yourself

  • The maximum running_cents per account equals SUM(net_amount_cents) over that account's term computed independently.
  • crossing_date is NULL if and only if the final running total is below the commitment.
  • term_elapsed_fraction falls in [0, 1] for every non-null crossing date.
  • running_cents is non-decreasing within each account, which fails immediately if a negative credit row was included without comment.

Follow-up

  • Turn this into an end-of-term overage forecast. What breaks if you extrapolate a linear run rate on a consumption product?
  • An account crosses its commitment at 40 percent of the term. Is that an expansion signal or a billing-surprise risk, and what would you check to tell them apart?

Seven-day activation rate by signup cohort week

Easy · funnel · cohorts · censoring

dim_account carries account_id, created_at, is_internal and is_current; fct_api_request carries account_id, request_at, http_status, api_key_id and traffic_class. Build a signup cohort by the ISO week of created_at over accounts with is_internal = false. An account counts as activated when it issues a request with http_status < 400, a non-null api_key_id and traffic_class <> 'synthetic_monitor' within 168 hours of its own created_at. Return cohort accounts, activated accounts and the rate per week, and exclude any week that has not yet fully elapsed its 168-hour window.

Approach

  1. Collapse dim_account to one row per account_id before anything else. It is a type 2 dimension, so a plan or status change gives the same account several rows; filtering to is_current = true is the cheapest correct choice here because created_at does not change across versions.
  2. Express the window as interval arithmetic on the timestamptz column: request_at >= created_at AND request_at < created_at + interval '168 hours'. A date-difference of 7 days is a different and wrong condition for accounts created mid-day.
  3. Test activation with EXISTS rather than a join to MIN(request_at). EXISTS short-circuits, keeps the cohort at one row per account, and cannot fan out.
  4. Aggregate by date_trunc('week', created_at AT TIME ZONE 'UTC'), counting accounts and activated accounts, and divide as a ratio of counts.
  5. Drop unreportable weeks: the last account in a cohort week is created just under week_start + 7 days, so the week is only complete once now() >= week_start + interval '14 days'. Without that filter the newest week always looks like a regression.

Follow-up

  • The median time-to-first-successful-call is more informative than a fixed-window rate. Why can you not compute it from this query, and what estimator does it need?
  • How would you separate accounts that never called from accounts that called and got only 4xx responses, and which of those is a product problem?

Given a choice between Airflow, Dagster, and dbt, how would you design a data transformation pipeline for complex energy portfolios?

Medium

Approach

  1. Say what you would check first and why it is the highest-information step.
  2. Work from the decision backwards to the evidence you would need.
  3. State your assumptions explicitly before working the problem.

Follow-up

  • How would you know your answer was wrong?
  • What assumption would you test first?

Design the retention metric suite for a quarterly board pack

Hard · net revenue retention · cohort arithmetic · as-of joins · gaming resistance

You own the retention numbers in the quarterly board pack. Build them from fct_subscription_period (account_id, arr_cents, term_start_date, term_end_date, amendment_type, booked_at, is_current, superseded_by_id) rather than from invoices. Deliver net revenue retention over a trailing twelve months, the guardrails that stop it being satisfied by shrinking the business, and the reporting lag you will enforce. Specify the cohort rule, state whether you report a ratio of sums or a mean of per-account ratios and why, and name two ways the headline number rises while the business gets worse.

Approach

  1. Freeze the cohort at month M-12: the set of accounts with ARR above zero on that date. No account acquired after M-12 enters either side of the ratio. That constraint is the entire point, because it makes the number a statement about the installed base rather than about how well sales did last quarter.
  2. Read arr_cents from the fct_subscription_period version that was live on each of the two dates, selected by term_start_date and term_end_date bracketing the date, never by is_current, which silently rates last year's revenue at this year's plan. Invoiced amounts move with billing_frequency and prepayment and will not reconcile to a contract-derived figure, so pick one source and say which.
  3. Report the ratio of sums: total arr_cents at M over the frozen cohort divided by total at M-12. The mean of per-account ratios is a different estimand and much noisier on a few thousand accounts, because contraction is bounded below at zero while expansion is unbounded, so a handful of large expansions dominate the mean.
  4. Attach the two guardrails that close the two gaming routes. Gross logo retention on the renewal-eligible base catches raising the ratio by declining to serve the churn-prone segment, because it is computed only over accounts that actually had an opportunity to leave. New-logo ARR reported beside it exposes a contracting top of funnel that the ratio is structurally incapable of seeing.
  5. Enforce the lag and the restatement policy. A 45-day grace on term_end_date for late paperwork means the most recent 45 days are never reportable, and a month is openly restated when the grace closes rather than quietly corrected between board packs.

Worked solution (40 min)

  1. Build an as-of ARR resolver: for a date D and an account, select the fct_subscription_period row where term_start_date <= D and term_end_date >= D, breaking ties on the latest booked_at so a superseded version never wins.
  2. Form the cohort at M-12 as accounts with as-of ARR above zero, then compute both sums with that account set held fixed.
  3. Compute the same quantity as a mean of per-account ratios and record the gap between the two.
  4. Build gross logo retention over accounts with term_end_date in each month, with the 45-day grace applied, and pull new-logo ARR from amendment_type = 'new' by booked_at.
  5. Recompute net revenue retention with the five largest accounts removed and report it beside the headline.

Expected result: Net revenue retention as a ratio of sums on a cohort frozen at M-12, reported beside gross logo retention on the renewal-eligible base and new-logo ARR, with the most recent 45 days marked not reportable. The answer names at least two ways the headline rises while the business worsens: ceasing to sell into the churn-prone segment, and expansion concentrated in a few accounts masking broad contraction underneath.

Verify it yourself

  • The cohort account count must be identical at M and at M-12; a larger count at M means accounts acquired inside the window leaked into the numerator.
  • A churned account must contribute zero to the numerator and its full M-12 ARR to the denominator, not be dropped from both, which would silently raise the ratio.
  • Net revenue retention recomputed without the five largest accounts must be reported; a headline that moves by more than a few points under that removal is a statement about those accounts rather than about the base.
  • Summed as-of ARR must not exceed the sum over is_current rows by more than the amendment rate; a large excess means the as-of join is returning several contract versions per account.

Follow-up

  • Compute net revenue retention both ways on the same cohort. What does a large gap between the ratio of sums and the mean of per-account ratios tell you about the shape of the expansion distribution?
  • A ramp deal is signed in March and starts in July. Which month does it belong to on the board's sales-effectiveness page, and which on this one?

Weekly active organisations fell nine percent over one week

Easy · seasonality · denominators · segmentation

A dashboard reports the weekly active organisation ratio on a trailing seven-day window ending each Wednesday. This week it reads nine percent below last week. You have fct_api_request (account_id, environment, traffic_class, http_status, request_at) and dim_account (account_id, billing_country, account_status, is_internal, is_current). Nothing was released. Decide whether usage actually fell, and hand back a corrected series plus a one-paragraph explanation that a non-analyst can repeat without you in the room.

Approach

  1. Count the holiday-free business days inside each window before comparing them. A trailing seven-day window spans exactly five weekdays wherever it ends, so its business-day count can only fall to four or fewer when a public holiday lands inside it and can never reach six, while usage in this domain follows a hard five-to-two weekday cycle. Two windows holding different numbers of business days are not comparable whatever the product did.
  2. Count distinct active accounts per calendar day for the last ten weeks and overlay the two windows. A calendar problem shows as a small number of weekdays sitting at weekend level, not as every day being uniformly lower.
  3. Cut the daily series by billing_country and index each country-day to that country's trailing same-weekday median, which isolates a regional public holiday from a product change.
  4. Check the denominator on its own: the metric divides by accounts whose account_status was in trial, free or active_paid for the whole week, so a batch suspension or status backfill moves the ratio with no change in the numerator at all.
  5. Report the series with each window's business-day count and the holiday dates annotated beside it, and state the residual week-over-week change that survives once the calendar effect is removed. Compare against earlier windows holding the same number of business days rather than dividing by business days, since distinct-account counts are sublinear in window length and dividing would over-correct.

Follow-up

  • Distinct account counts are sublinear in the number of days in the window. Why does losing one of five business days reduce the count by noticeably less than twenty percent?
  • How would you make this metric comparable across countries with different holiday calendars without hand-maintaining a holiday table forever?

Suggested practice checklist

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.

Day 1 — Breadth 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.

Day 2 — Breadth 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.

Day 3 — Breadth 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.

Day 4 — Breadth 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.

Day 5 — Depth, 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.

Day 6 — Depth, 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.

Day 7 — Integration 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.

Bring numbers, including the ones you are unsure of

An answer without a quantity is hard to interrogate, so interviewers keep probing until they find one. Come with the baseline, the change, the window it was measured over, and how confident you were. If the effect never got measured, say so and say what you would have measured. Fabricated precision is worse than an honest gap.

Describe your experience with CI/CD for machine learning. How do you handle model versioning and monitoring in production?

Medium · behavioural and stakeholder questions

Approach

  1. Name the disagreement or constraint, and how you resolved it with evidence.
  2. State the situation in two sentences and spend the rest on your reasoning.
  3. Pick a story where you drove the decision, not one where you observed it.

Follow-up

  • What would you do differently if you ran that project again?
  • How did you know the outcome was caused by your change?

How do you balance the need for speed in a high-growth startup with the need for technical precision and thoroughness?

Medium · behavioural and stakeholder questions

Approach

  1. Name the disagreement or constraint, and how you resolved it with evidence.
  2. Close with what you would do differently, concretely.
  3. Pick a story where you drove the decision, not one where you observed it.

Follow-up

  • How did you know the outcome was caused by your change?
  • What did you decide not to do, and why?

State the measured impact of your own work honestly

Hard · impact · causal inference · self-assessment

You are asked for the business impact of a renewal-risk worklist you shipped nine months ago. Customer success used it, and renewals in the covered segment came in 6 points above the prior year. Coverage was assigned by the team itself: they worked the top of your list and also the accounts they were already worried about. Produce the impact claim you are willing to defend, the number you refuse to claim, and the design you would have asked for at the start.

Approach

  1. The interviewer is probing whether you can separate what you shipped from what you caused, and whether you would have built the measurement in rather than reconstructing it afterwards. Both halves are being scored.
  2. Name the confound precisely. Coverage assignment is doubly selected: the largest accounts get an owner because they are valuable, and distressed accounts get one because they are at risk. The naive covered versus uncovered comparison mixes a strong positive selection with a strong negative one and can come out with either sign depending on which rule dominated. Matching on account size does not fix it, because the risk signal that triggered coverage is the same signal that predicts the outcome.
  3. Split the claims by what each needs to be true. Ranking quality is defensible from precision at k on out-of-time renewals. Adoption is defensible from timestamps showing what share of listed accounts were contacted. The outcome claim is not defensible without a design, and saying so is the point of the exercise.
  4. Look for identification before giving up on it. A capacity cut-off, a territory boundary, or a period in which the list existed but was unstaffed can assign coverage for reasons unrelated to account health, and any of those supports a bounded estimate.
  5. State the design you would ask for now and its price: a randomly withheld slice of the list, held for two renewal quarters, with the expected cost in renewals stated openly. That cost is what it takes to be able to answer this question at all.
  6. Give a bounded number rather than none. Six points with an explicit statement of how much of it you can attribute is more useful than either claiming the whole figure or declining to quantify anything.

Follow-up

  • Your manager wants the 6 points in a promotion packet. What wording do you accept, and what do you strike?
  • What would have had to be true for the naive covered versus uncovered comparison to be valid?
  • If the holdout costs the team real renewals, how do you justify asking for it, and to whom?