Interview guides · Codvo.ai
Codvo.ai Data Scientist Interview Questions & Guide 2026
A two-week prep plan for Codvo.ai data scientist candidates: write your answer before reading the solution, revisit misses three days later.
The 60-second brief
As a Data Scientist at Codvo.ai, you are at the intersection of advanced analytics and digital transformation. You will not merely be building models in a vacuum; you will be architecting intelligence that powers Data Center Intelligence, Connected Vehicle ecosystems, and high-stakes Advanced Data Analytics solutions. Your work directly influences how Codvo.ai delivers value to its clients, transforming raw streams of information into actionable business foresight.
Scope your preparation by the data you would actually touch, because the title will not tell you. A seat that lives in event logs and weekly readouts rewards fluency in aggregation and metric definitions; a seat that owns a model in production rewards fluency in train/serve skew, retraining cadence and drift monitoring. The fastest way to find out which one you are interviewing for is to ask what the team shipped last quarter and what it gets paged about.
Focus areas
- Read NRR on a fixed account cohort
- Measure churn only on renewal-eligible accounts
- Separate contracted seats from actively used seats
Overview
This role requires a unique blend of technical rigor and strategic mindset. You will be expected to navigate complex, often ambiguous datasets to solve real-world problems that have immediate commercial impact. Whether you are optimizing operational efficiency for data centers or developing predictive models for automotive telemetry, your output is a core component of the Codvo.ai product suite. You will work closely with cross-functional teams, ensuring that your data-driven insights are not just accurate, but also scalable and deeply integrated into the company’s broader engineering initiatives.
The role is highly remote-friendly, but successful candidates demonstrate a high degree of self-motivation and the ability to articulate complex technical concepts to non-technical stakeholders across different time zones.
The Codvo.ai interview process
Codvo.ai candidates report 5 rounds · ≈ 4-6 weeks. The stages below are what candidates describe, not a published process.
Recruiter Screen
Whoever runs this call is usually not a practitioner. They take notes, and a hiring manager skims those notes later, so the real question is whether your work survives being written down by someone outside the field. Test every project sentence against that: could a non-specialist repeat it correctly without knowing what a propensity score is? Carry a plain-language version of each project and one reason you want this particular role that you could not copy onto another application. Vagueness at this stage reads as inexperience, even when the underlying work was genuinely deep.
What to demonstrate
- Whether a non-specialist can restate your projects accurately, since their paraphrase is what reaches the hiring manager
- Whether your reason for wanting the role points at the work itself rather than the company's reputation
- Whether your language signals the level being screened for: what you decided yourself versus what you were handed
How to prepare
- Write a two-sentence, jargon-free version of each major project: the question nobody could answer, and the decision your work changed. Read it to someone outside data and have them repeat it back
- Point your 'why this role' answer at something concrete in the job description or the product surface you would be working on, and keep it to two sentences
- Have two questions ready about measurement: which metric the team is held to, and who acts on an analysis once it lands
Technical Deep-Dives
This round decides whether someone can hand you a schema and a question and trust the number that comes back. Correctness under a clock is the bar, not clever syntax. The habit that separates strong from weak answers is checking the grain: after every join, know how many rows you expect and whether the count moved. Most wrong answers in this format are not wrong logic, they are a fan-out from a key that turned out not to be unique, or a filter applied before an aggregate when it belonged after. Say what you expect before you run it.
What to demonstrate
- Whether your row counts survive each join, and whether you notice on your own when they do not
- Deliberate handling of rows that fail to match, including whether the question needs an inner join or a left join with the non-matches kept and counted
- Whether NULLs are treated on purpose, given that a NULL compares equal to nothing and that COUNT of a column skips it
- Reaching a defensible answer inside the window instead of a refined one after it
How to prepare
- Take a two-table schema, write a join that fans out on purpose, then fix it by collapsing the many-side to one row per key before joining. Repeat until the fix is reflex rather than recall.
- Write a funnel as one query and print the distinct user count at each stage, then confirm each stage is a subset of the one above it rather than assuming it
- Do a few timed runs in a plain text box with no autocomplete and no formatter, since assessment editors often have neither
Behavioral Interviews
This round decides whether you owned a decision or watched one happen nearby. Interviewers for data roles listen for the point where the analysis stopped being a report and started changing what someone did, so build each story around that hinge: what was going to happen by default, what you found, and what happened instead. The most common weakness is a story that ends at delivery. If you can name the decision your work changed and the number that moved because of it, most follow-ups become easy.
What to demonstrate
- Whether the decision was yours to influence, or whether you are narrating a team outcome in the first person
- The counterfactual: what would have been done without your analysis, and why that default was worse
- How far your involvement ran past the handoff, and whether you checked that the change did what you predicted
How to prepare
- Pick three projects and write one sentence for each naming the decision-maker, the choice in front of them, and what they chose after seeing your work. If you cannot name a person and a choice, the story is not ready for this round.
- Reconstruct the baseline for your strongest project from the original query or dashboard rather than memory, so the before-number survives a follow-up asking where it came from.
- Prepare an honest version of a project where your recommendation was overruled, including what you did with the analysis afterwards.
Live Coding Sessions
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
Case Studies
A case has a fixed clock, and a good deal of what is being scored is how you spend it. Thirty to forty-five minutes buys one pass across the whole problem or a deep read of one part of it, and choosing between those is the work rather than a compromise forced on you. Announce the shape early: the structure you are using, the branch you think carries the decision, and what you are setting aside. An answer that is thorough for the first third and silent on the recommendation reads worse than one that is rougher throughout and lands.
What to demonstrate
- Whether a visible structure appears in the opening minutes and survives the rest of the case
- Whether the depth goes to the branch that carries the decision, rather than the branch you find most comfortable
- Whether you say what you are leaving out and why, instead of quietly omitting it and hoping nobody asks
How to prepare
- After each practice case, write down the branches you chose not to open and the reason for each, then check whether you said any of them out loud while the case was running. A branch you only cut privately reads to the interviewer as one you missed.
- Redo a case you have already worked in half the time, deciding in advance which single branch you keep, then compare which version a listener would find more useful.
- Write a two-sentence opening you can reuse, holding the restated question and your plan for the available time, and deliver it within the first ninety seconds of every practice run.
Where candidates lose points
Reporting a mean over accounts when account revenue is heavy-tailed
When a small number of accounts hold most of the revenue, the sample mean is dominated by whichever of them happens to be in the sample, and the sample variance keeps growing as more data arrives instead of stabilising. In that regime the usual central-limit-based confidence interval understates uncertainty, and a single renewal or a single large account's batch job can flip the sign of a measured effect. The fixes are to pre-register a winsorisation or capping rule before looking at the outcome, to report account counts crossing a threshold alongside the revenue figure, or to define the estimand on a bounded transform. Choosing the cap after seeing the result is a separate and worse problem, because the cap then encodes the answer.
Randomising an experiment at the user level when users share an account
Two problems fire at once. Colleagues in one workspace see each other's work and talk to each other, so a treated user changes the behaviour of a control user in the same account, which violates the no-interference assumption and biases the estimate toward zero. Separately, outcomes within an account are strongly correlated, so the effective sample size is roughly n / (1 + (m - 1) * rho) for m users per account and intra-class correlation rho, not n. With rho around 0.3 and twenty users per account that is a design effect near 6.7, meaning a user-level confidence interval is about two and a half times narrower than it should be and results cross significance thresholds on noise alone. Randomise the account and cluster the standard errors.
Reading experiment results before checking the arm split
Compare observed arm counts against the intended allocation ratio, not an assumed even split, and set the alarm far below the conventional 0.05: at 0.05 roughly one healthy experiment in twenty trips it, which is why sample-ratio checks usually run at p < 0.001 or stricter. The test's power scales with sample size, so it misses a real diversion on a small experiment and fires on an imbalance too small to move the estimate on a very large one. A flag means go find the assignment or logging fault before reading any outcome, not report a mismatch.
Solving silently instead of narrating the reasoning
Say which branch you are taking and why you chose it over the alternative, for example checking the denominator first because it changes what the comparison means. A correct answer that arrives with no visible path scores below a rigorous one that needed a hint.
Questions & practice
How would you approach building a predictive maintenance model for a connected vehicle fleet?
Medium · machine learning and modelling
Approach
- Frame the prediction: the label, the moment of prediction, and the action it triggers.
- Set a baseline first, so any model has something honest to beat.
- Say how the offline result would be validated online before it is trusted.
Follow-up
- How would you choose the decision threshold, and who owns that choice?
- What would you monitor after launch to know the model is still valid?
If a client reports that our model's performance has degraded, how do you troubleshoot the issue?
Medium · machine learning and modelling
Approach
- Pick an evaluation metric that matches the cost of each error type, not a default.
- 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.
Follow-up
- What would you monitor after launch to know the model is still valid?
- Where could label leakage enter this setup?
Explain the trade-offs between different machine learning models for a classification problem.
Medium · machine learning and modelling
Approach
- Pick an evaluation metric that matches the cost of each error type, not a default.
- Set a baseline first, so any model has something honest to beat.
- Say how the offline result would be validated online before it is trusted.
Follow-up
- Where could label leakage enter this setup?
- How would you choose the decision threshold, and who owns that choice?
Implement seven-day activation rate from its written definition
Easy · metrics · pandas · activation
Given dim_account (account_id, created_at, is_internal, is_current) and fct_api_request (account_id, request_at, http_status, api_key_id, traffic_class), implement activation_rate(accounts, requests, week_start). Definition: the numerator is accounts whose first request with http_status < 400, api_key_id not null and traffic_class != 'synthetic_monitor' occurs no later than 168 hours after created_at; the denominator is non-internal accounts created during the ISO week starting week_start. All timestamps are tz-aware UTC. Return the rate and both counts, and refuse to report a week until every account in it has had its full 168 hours.
Approach
- Reduce dim_account to one row per account_id before joining anything. It is a type 2 dimension, so several versions of the same account exist; joining the versioned table to requests multiplies the denominator by the number of plan changes an account happened to make.
- Build the denominator first and freeze it: is_internal == False and week_start <= created_at < week_start + 7 days. Everything after this is a filter on the numerator only, because an account that never sent a request must still sit in the bottom of the fraction.
- Filter requests to qualifying rows and only then take groupby('account_id').request_at.min(). The first qualifying request is not the same object as the global first request filtered afterwards, and the two answers differ for every account whose first call was a 4xx.
- Left-join the first qualifying timestamp onto the cohort and test (first_ok - created_at) <= Timedelta(hours=168). NaT propagates to False in that comparison, which is the behaviour you want, but assert it rather than assume it.
- Guard reportability explicitly: if week_start + 7 days + 168 hours exceeds the maximum request_at in the data, the week is censored and will read as a drop, so return None or raise rather than emit a number.
Worked solution (20 min)
- cohort = accounts.loc[~accounts.is_internal & accounts.created_at.between(week_start, week_start + pd.Timedelta(days=7), inclusive='left'), ['account_id','created_at']].drop_duplicates('account_id')
- ok = requests[(requests.http_status < 400) & requests.api_key_id.notna() & (requests.traffic_class != 'synthetic_monitor')]
- first_ok = ok.groupby('account_id', as_index=False).request_at.min(); m = cohort.merge(first_ok, on='account_id', how='left'); assert len(m) == len(cohort)
- activated = (m.request_at - m.created_at) <= pd.Timedelta(hours=168); return {'rate': float(activated.mean()), 'numerator': int(activated.sum()), 'denominator': len(m)}
Expected result: A dict whose denominator is the count of distinct non-internal accounts created in that ISO week, numerator <= denominator, and rate = numerator / denominator; accounts with no qualifying request sit in the denominator with NaT and evaluate to False.
Verify it yourself
- len(m) == cohort.account_id.nunique(); the merge must not add rows.
- Dropping the synthetic_monitor filter can only raise the numerator, never lower it.
- Widening the window from 168h to 336h is monotone: the numerator must not decrease.
- An account whose only qualifying request falls at exactly created_at + 168h counts as activated, since the comparison is inclusive.
Follow-up
- Median time-to-first-call is more informative. What breaks if you take the median over activated accounts only, and what estimator fixes it?
- How would you decide whether 168 hours is the right window rather than 72 or 336?
- An account signs up, does nothing for 20 days, then integrates heavily. Where does it land in this metric, and is that what you want?
Collapse retried requests into logical operations per account
Medium · window functions · deduplication · null semantics
fct_api_request carries request_id, account_id, api_key_id, idempotency_key (null when the caller supplied none), is_retry, request_at and http_status. For one ISO week, count logical operations rather than HTTP calls: requests sharing (account_id, api_key_id, idempotency_key) collapse to the earliest one, while every request with a null idempotency_key is its own operation. Return per account the raw request count, the logical operation count and the ratio between them, and state which direction a rising 5xx rate pushes that ratio.
Approach
- Split the stream before deduplicating. GROUP BY and PARTITION BY treat NULLs as equal to one another, while the = operator does not, so a single grouping pass folds every null-key request in an account into one 'operation' and can cut the count by orders of magnitude.
- Deduplicate the keyed rows with ROW_NUMBER() OVER (PARTITION BY account_id, api_key_id, idempotency_key ORDER BY request_at, request_id) and keep rn = 1. Including request_id in the ORDER BY makes the result deterministic when two rows share a millisecond, which they will.
- UNION ALL the null-key rows back in unchanged; they need no dedup and must not pass through the partition.
- Aggregate per account: count() over the raw week, count() over the union, and the ratio of the two. Report the ratio, not the difference, so accounts of very different sizes are comparable.
- Cross-check against is_retry, which flags a resend of the same idempotency key: raw minus logical should sit close to the count of is_retry rows, and a large gap means clients are resending without a key and the dedup is under-counting duplicates.
- Name the inversion explicitly: 5xx responses provoke client retries, so amplification rises exactly when reliability falls, and any engagement metric built on raw request counts will show growth during an outage.
Follow-up
- Which of these two counts belongs in the billable-units metric, and which in an engagement metric?
- An account's amplification ratio jumps from 1.05 to 3.0 in a day. Name three causes and the query that separates them.
Monthly account margin joined as-of the live contract version
Medium · as-of join · fan-out · scd2 · unit economics
fct_usage_daily carries account_id, usage_date, net_amount_cents and cogs_cents. fct_subscription_period carries account_id, plan_tier, term_start_date, term_end_date, booked_at and is_current, with one row per contract version, so several superseded versions can bracket the same usage_date. dim_account is a type 2 dimension keyed on account_id with employee_band, is_internal, effective_from, effective_to and is_current. Return monthly net revenue, allocated COGS and gross margin per account, labelled with the plan_tier in force during that month and the employee_band in force at month end. The reported figure excludes internal accounts, so it cannot equal the raw sum of net_amount_cents; reconcile additively instead, quantifying every bucket you drop so the reported and dropped amounts add back to that raw sum.
Approach
- Aggregate fct_usage_daily to one row per (account_id, month) before touching any dimension. Aggregating after the join is what multiplies revenue, and no amount of DISTINCT afterwards recovers the right number.
- Resolve the contract as-of the month rather than as-of now. Take fct_subscription_period rows whose term brackets the month, then rank with ROW_NUMBER() OVER (PARTITION BY account_id, month ORDER BY booked_at DESC, subscription_period_id DESC) and keep rank 1. Filtering on is_current instead backdates today's plan over last year's usage and quietly rewrites history.
- Resolve dim_account with the half-open interval effective_from <= month_end AND (effective_to > month_end OR effective_to IS NULL). The NULL on the live version has to be spelled out or the current row drops out of every recent month.
- Compute margin as (sum(net_amount_cents) - sum(cogs_cents)) / NULLIF(sum(net_amount_cents), 0) at the account-month grain, and report the distribution rather than a blended rate so margin-negative accounts stay visible.
- State the month-straddling rule explicitly: either split the month at the amendment date or take the version in force at month end. Both are defensible; an unstated choice is not.
- Reconcile additively, not by equality to the unfiltered total. The output deliberately drops two populations: account-months whose as-of dim_account version carries is_internal = true, and account-months with no fct_subscription_period version bracketing the month, which an inner join on the contract removes without saying so. The statement that ties out is reported_net + internal_net + unmatched_net = sum(net_amount_cents) over the same usage_date range. Plain equality to the raw sum would only hold if both dropped buckets were empty, and the internal one never is.
- Compute the internal bucket with the identical as-of rule used for the output, evaluating is_internal on the dim_account version in force at that month end. Reading is_internal from the current version instead moves accounts between the two sides of the identity and it stops closing.
Worked solution (30 min)
- Build usage_monthly: SELECT account_id, date_trunc('month', usage_date) AS month, sum(net_amount_cents) AS net_cents, sum(cogs_cents) AS cogs_cents FROM fct_usage_daily GROUP BY 1, 2.
- Build contract_asof by joining usage_monthly to fct_subscription_period on account_id with the term bracketing the month, then applying the ROW_NUMBER ranking on booked_at DESC and keeping rn = 1.
- Join dim_account with the half-open effective_from/effective_to predicate evaluated at month end, and filter is_internal = false on the version selected.
- Select account_id, month, plan_tier, employee_band, net_cents, cogs_cents and (net_cents - cogs_cents)::numeric / NULLIF(net_cents, 0) AS gross_margin.
- Run the reconciliation as a three-way identity over the same usage_date range: sum(net_cents) in the output, plus sum(net_cents) over account-months whose as-of dim_account version has is_internal = true, plus sum(net_cents) over account-months with no bracketing subscription version, must equal SELECT sum(net_amount_cents) FROM fct_usage_daily for that range, to the cent. Publish the two dropped amounts next to the total rather than leaving them implicit; a non-zero unmatched bucket is a contract-coverage bug to chase, not rounding.
Expected result: One row per account per month with net revenue, allocated COGS, gross margin, and the plan_tier and employee_band that were actually in force, with output row count equal to the number of distinct (account_id, month) pairs that survive both the internal-account filter and the contract-bracket join.
Verify it yourself
- Output row count equals COUNT(DISTINCT (account_id, date_trunc('month', usage_date))) on the source fact restricted to the same two filters; counted against the unrestricted fact it is strictly smaller, by exactly the internal and unmatched account-months.
- reported_net + internal_net + unmatched_net equals the raw sum from fct_usage_daily over the same usage_date range, to the cent, with all three terms computed from the same usage_monthly CTE.
- The reported figure alone is strictly below the raw sum whenever any internal account recorded usage in the window, and equal to it only in a window where none did.
- No account-month carries more than one plan_tier or employee_band value.
- Accounts with net_cents of zero return NULL margin rather than a division error, and margin-negative accounts appear with a value below zero rather than being dropped.
Follow-up
- An account amends mid-month from team to enterprise. Show what your query reports and argue for one attribution rule over the other.
- Your monthly total disagrees with the finance figure by a small amount. Where would you look first, and which number do you defend?
Given a massive, unstructured dataset, what are the first three things you would do to explore it?
Medium · product sense and metrics
Approach
- 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.
- Restate the decision this analysis has to support, and who acts on the answer.
Follow-up
- What would you do if the primary metric and the guardrail moved in opposite directions?
- How would you detect that the metric is being gamed rather than genuinely improving?
How would you design a metric to measure the success of a new predictive feature?
Medium · product sense and metrics
Approach
- Restate the decision this analysis has to support, and who acts on the answer.
- Fix the population and the time window before naming any metric.
- Decompose the metric into the rates that drive it, and say which one you would check first.
Follow-up
- 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?
Triage a sample ratio mismatch before reading the result
Easy · srm · assignment · instrumentation
An account-randomised test assigned arms 50/50 by hashing account_id. Exposure logging recorded 4,200 accounts: 1,987 in treatment and 2,213 in control. A chi-square goodness-of-fit test against the intended 2,100/2,100 split gives chi-square(1) = 12.2, p about 0.0005. The analyst reports the primary metric up 6% in treatment and asks to ship. Explain what the imbalance implies about that 6%, rank the causes you would investigate first given this domain's data, and name for each the single query against fct_api_request or dim_account that confirms or eliminates it.
Approach
- State the rule before diagnosing anything: a sample ratio mismatch below a 0.001 alarm threshold invalidates the readout. Whatever mechanism removed 113 accounts from one arm almost certainly removed them non-randomly, which makes it a selection effect on the outcome. The 6% is not to be adjusted, caveated or shipped; it is unusable until the mechanism is named.
- Verify the test is testing the right thing. chi-square = (1987 - 2100)^2 / 2100 + (2213 - 2100)^2 / 2100 = 6.08 + 6.08 = 12.16 on one degree of freedom. Confirm the denominator is the intended assignment count rather than the observed total, and that you are testing assignment rather than an analysis population that has already been filtered.
- Rank causes by how this domain actually breaks rather than by textbook order: is_internal accounts filtered after assignment instead of before; exposure logged on a code path that returns 5xx more often in one arm, silently dropping those accounts; assignment recorded at first request but exposure at a later event, so accounts that churned in between appear in only one arm; dim_account type-2 versioning giving one account_id two surrogate keys and two hash inputs; deployment_model = 'self_hosted' accounts that never emit exposure telemetry at all.
- Attach one discriminating query to each: count arms before the is_internal filter; compare the http_status >= 500 rate by arm on the exposure endpoint in fct_api_request; compare the distribution of created_at and churned_at by arm in dim_account; count distinct account_sk per account_id inside the experiment window; cross-tabulate arm against deployment_model.
- Compare counts at three fixed stages, assignment, first exposure, and analysis population, and localise the divergence to one of them. The stage where the arms first separate names the subsystem, and everything downstream of it is a symptom.
- Report the outcome as abort, fix, rerun, and be explicit about what survives: the variance estimate for re-powering, the instrumentation fix, and nothing whatsoever about the effect size.
Worked solution (20 min)
- Recompute the statistic: two terms of 113^2 / 2100 = 6.08, total 12.16, p about 0.0005 on one degree of freedom, below the 0.001 alarm line.
- Pull arm counts at assignment, at first exposure, and in the analysis population, and find the first stage at which they diverge.
- At that stage, cross-tabulate arm against is_internal, deployment_model, and the 5xx rate on the exposure endpoint.
- Write the abort note naming the mechanism, the corrected code path, and the rerun date, with the effect estimate explicitly withheld.
Expected result: chi-square(1) = 12.16, p about 0.0005, so a 0.001 alarm threshold fires and the readout is abandoned. The divergence is localised to a single pipeline stage by comparing arm counts at assignment, at exposure and at analysis, and the most likely causes in this domain are post-assignment internal-account filtering or exposure logging that fails asymmetrically on 5xx responses.
Verify it yourself
- A 113-account shortfall on 2,100 expected is a 5.4% deficit in one arm, which under a sound hash is roughly a 1-in-2,000 event. That is what the 0.001 threshold is calibrated to catch.
- Counts at the assignment stage should already be balanced if the bucketing is correct. If they are not, the bug is in the hash or the salt, not in the logging.
- Any fix that restores balance by filtering on a variable measured after exposure has conditioned on a post-treatment variable and reintroduces the bias it appears to remove.
Follow-up
- The imbalance disappears once you restrict to accounts with at least one successful request. Does that fix the experiment or confirm the bug?
- What alarm threshold would you set for this check, and why is 0.05 the wrong one for a diagnostic you run on every experiment every day?
- How would you detect a mismatch confined to one segment while the overall split looks clean?
Consumption per account always dips in the last four days
Easy · pipeline lag · restatement · reporting windows
Your consumption dashboard reads billable units per paying account from fct_usage_daily (account_id, sku_code, usage_date, billable_quantity, net_amount_cents, is_restated, first_written_at, restated_at, updated_at). Every refresh shows the final three to five days declining, and a review is scheduled on that shape. Establish empirically how long a usage_date takes to settle, separately per sku_code, and specify the trailing exclusion window every reported figure should use. Derive the number from the data; do not adopt a convention.
Approach
- Treat settlement as a measurable curve rather than a belief: for each historical usage_date, compare the total captured as of age d days against that date's final settled total.
- Do it per sku_code, because metering paths differ. A per-request SKU lands within hours, while a storage-month SKU is produced by a daily sweep and lands later, so one global lag number is wrong for at least one of them.
- Pick the age at which a stated percentile of dates reaches a stated completeness threshold, for example the tenth percentile date reaching 99.5 percent of final, and take the slowest SKU's age as the dashboard's exclusion window.
- Separate late arrival from restatement. Late rows raise totals and are fixed by waiting; restatements can move either way and are not, so report the share of the gap from each.
- Encode the exclusion inside the query rather than in the chart, so anyone reusing the SQL inherits the rule, and never compare a fresh partial period against a settled one.
Follow-up
- Month-end invoicing needs a number before settlement completes. How would you publish an early estimate with an honest uncertainty band attached?
- What monitor would tell you the settling time has changed, without anyone remembering to re-run this analysis?
Suggested practice checklist
Roughly 90 minutes a night on weekdays with one longer weekend block. The plan deliberately cuts scope rather than compressing everything, on the assumption that finishing one thing a night beats half-starting four.
Day 1 — Fix the scope and set a baseline
- Read the role description and write the three things the loop will almost certainly test, then write an explicit not-doing list for everything else and keep it visible all week.
- Take one 20-minute SQL prompt and one 10-minute metric question cold, and write the single sentence that says what blocked each attempt, since that sentence is what decides which two topics get the most evenings.
- Set the week's one rule: one problem finished to completion every night, including the night you only have 40 minutes.
Deliverable: A one-page scope with an explicit not-doing list and two cold attempts, each carrying one sentence on what blocked it.
Day 2 — One query pattern, written three times
- Choose the single pattern most likely to appear (a cohort retention grid, or a funnel counted by user) and write it three times from a blank file rather than editing the previous attempt.
- On the third attempt, write the grain of every CTE as a comment before writing its body.
- Stop at 90 minutes even if the third version is imperfect, and write the one thing you would fix with another hour.
Deliverable: Three independent versions of the same query plus a note on what changed between them.
Day 3 — Only the statistics you will be asked to defend
- Write, in under 200 words, how you would decide whether a difference between two groups is real: the test, its assumptions, and what you would switch to when an assumption fails.
- Compute a 95 percent confidence interval for a difference in proportions by hand on realistic numbers, then write in one sentence what changes if the two samples are paired rather than independent.
- Write your answer to "what does a p-value mean", check it against a definition, and delete the version that describes it as the probability the hypothesis is true.
Deliverable: A 200-word written answer and one hand-computed interval you can reproduce under pressure.
Day 4 — One case, and the assumptions holding it up
- Answer one product case aloud in 20 minutes with a recording running, then listen back with a pen and mark every claim you asserted without saying what it rested on: an assumed user behaviour, an assumed data source, an assumed baseline rate, an assumed grain.
- Pick the three assumptions the recommendation actually depends on, write how you would check each one against data, and say which one being wrong would flip the recommendation rather than merely weaken it.
- Write the four-step structure you used onto a card small enough to hold in working memory when you are nervous.
Deliverable: One recording, three load-bearing assumptions each with a written check, and a four-step structure card.
Day 5 — Your own work, timed
- Write a 90-second version and a four-minute version of your main project, and time both out loud rather than reading them.
- Prepare answers to the two follow-ups that always come: what you would do differently, and how you knew it worked.
- Put one number in the first sentence and be able to say exactly where that number came from and what it excludes.
Deliverable: Two timed narratives with one defensible number in the opening line.
Day 6 — The one full rehearsal, in a longer weekend block
- Run a 60-minute mock covering query work, a case and a behavioural question in a single sitting with no breaks, because sustained attention is the thing evenings have not trained.
- Immediately afterwards, and before hearing any feedback, write the three moments you lost the thread.
- Spend the rest of the block only on those three moments, and on nothing you merely feel shaky about.
Deliverable: Mock notes naming three failure moments with a specific fix written under each.
Day 7 — Taper
- Write the 20-minute warm-up you will actually do on the morning of the interview: one query you can already write from a blank file, one metric you can define out loud, and nothing you have never seen before.
- Re-read only your own notes from this week, and open no new material.
- Write down the logistics: the tool you will be asked to work in, whether lookups are allowed, and the sentence you will use when you do not know something.
Deliverable: A one-page card holding the case structure, the project numbers, and the logistics.
Where your data came from
Data people depend on systems owned by other teams, and much of the job is negotiating for instrumentation, access, or a fix to a broken pipeline. Prepare an example of getting something changed upstream that you did not control. Describe what you asked for, what you traded, and how you worked while you waited.
How do you handle missing or noisy data in a production environment?
Medium · behavioural and stakeholder questions
Approach
- State the situation in two sentences and spend the rest on your reasoning.
- Name the disagreement or constraint, and how you resolved it with evidence.
- Close with what you would do differently, concretely.
Follow-up
- What would you do differently if you ran that project again?
- How did you know the outcome was caused by your change?
Describe a time you had to explain a complex statistical finding to a non-technical stakeholder.
Medium · behavioural and stakeholder questions
Approach
- Close with what you would do differently, concretely.
- Quantify the outcome, including what you would not claim credit for.
- Name the disagreement or constraint, and how you resolved it with evidence.
Follow-up
- What would you do differently if you ran that project again?
- What did you decide not to do, and why?
Report an underpowered consumption test to a non-technical executive
Medium · communication · uncertainty · experimentation
An account-randomised packaging change ran six weeks across 900 paying accounts. The effect on billable units per account per month is plus 4.1 percent, with a 95 percent interval from minus 3.2 to plus 11.8 after clustering standard errors at the account and applying the pre-registered winsorisation at the 99th percentile. An executive with no statistical background wants one number this week to decide a full rollout. Produce a three-sentence spoken answer, one chart, and an explicit recommendation of ship, stop or keep running, with the cost of each option stated.
Approach
- The interviewer is probing whether you can be decision-useful without either hiding the uncertainty or hiding behind it. Start from the decision rather than the statistics: establish what the executive would do differently at plus 4 percent versus zero, because if the action is identical the interval does not matter.
- Translate the interval into consequences in units the executive already reasons about. Multiply both endpoints by the cohort's baseline consumption and contracted rates to give an annualised revenue range, so the answer is a range of dollars rather than a range of percentages.
- Price the option to wait. Using the observed variance, state roughly how many additional account-weeks halve the interval width, so keep running becomes a quantified choice instead of a stall.
- Offer a cheaper path to the same decision: a lower-variance proximate outcome such as successful billable units on the new SKU, or CUPED using each account's pre-period consumption, quoting the expected variance reduction as one minus the squared pre-post correlation.
- Give a recommendation and name the single observation that would reverse it. A strong answer commits; a generic one recites the interval and leaves the decision on the table.
Follow-up
- The executive says it clearly works and is just not provable, so ship it. What is your answer?
- How much of the interval width comes from clustering and how much from the revenue tail, and what would you do about each?
- If you had to ship this week with no more data, which guardrail would you watch for the first fortnight and at what threshold would you roll back?