Interview guides · A.O. Smith
A.O. Smith Data Scientist Interview Questions & Guide 2026
Case practice for A.O. Smith data scientist candidates: scope a vague prompt into a measurable question before you analyze it.
The 60-second brief
As a Data Scientist at A.O. Smith, you occupy a pivotal role at the intersection of traditional manufacturing excellence and modern digital transformation. You are responsible for leveraging data to drive innovation across a global portfolio of water heating and treatment solutions. Your work directly influences product efficiency, operational performance, and the customer experience, moving the company toward a more connected, intelligent ecosystem.
Learn the economics of the product category before the loop. Marketplaces, subscription products and ad-supported products turn on different core quantities (match rate and liquidity, retention and churn, fill rate and yield) and fail in different characteristic ways.
Focus areas
- Define numerator, denominator and window precisely
- Pick a randomisation unit that respects interference
- Turn a vague request into a measurable question
Overview
You will encounter complex challenges that require a blend of rigorous statistical analysis and practical business acumen. Whether you are optimizing supply chain logistics, predicting maintenance needs for hardware, or integrating generative AI into customer-facing applications, your impact is tangible. Success in this role requires not just technical proficiency, but the ability to translate data-driven insights into actionable strategies that align with A.O. Smith’s long-standing commitment to quality and innovation.
Where to focus your preparation
Treat the sections below as preparation areas and confirm the format with your recruiter.
Preparation focus
No round sequence has been reported for this company, so work the categories below and confirm the format with your recruiter.
What to demonstrate
- Breadth across SQL, experimentation and product reasoning
- Ability to state assumptions before choosing a method
How to prepare
- Drill the practice exercises below and time yourself
- Prepare three quantified stories about decisions you drove
Where candidates lose points
Counting on an identity key that changes underneath the metric
visitor_id is per browser and per device, and it resets on cookie clearance, private browsing and platform privacy changes, so the distinct-visitor count drifts upward for reasons unrelated to reach. Any rate with visitors in the denominator therefore decays over time even when behaviour is constant, and any rate with visitors in the numerator inflates. The stitching at signup makes it worse in both directions: a user who signed up on mobile and returns on desktop is two visitors and one user, while a shared device is one visitor and several users. Decide which key each metric is counted on, write it into the definition, and when comparing a period before and after a platform privacy change, expect a level shift in every visitor-keyed metric and do not attribute it to the product.
Watching an experiment daily and stopping when it crosses significance
A fixed-sample test controls type I error at one pre-declared look. Checking repeatedly and stopping at the first p < 0.05 inflates the false positive rate to roughly 0.15 to 0.20 for ten looks, and it rises further with more frequent checks, because the p-value takes a random walk that will eventually dip below the threshold under the null. The usual defences are a fixed horizon declared before launch, group-sequential boundaries such as O'Brien-Fleming that spend alpha across a planned number of looks, or always-valid confidence sequences that are correct under continuous monitoring. Compounding it, the effect size reported conditional on having crossed the threshold is biased away from zero, and the bias is larger the lower the power was, so an underpowered test that 'won' typically overstates the lift it found.
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.
Analysing at a different unit than the one randomised
Say out loud what was randomised (user, device, account, cluster) and make the analysis unit match, or account for the clustering with cluster-robust standard errors, the delta method, or aggregation up to the randomised unit. Randomising users and then running a test over sessions understates variance and inflates the false-positive rate.
Questions & practice
What are the common use cases for Gradient Boosting?
Medium · machine learning and modelling
Approach
- Set a baseline first, so any model has something honest to beat.
- Pick an evaluation metric that matches the cost of each error type, not a default.
- Say how the offline result would be validated online before it is trusted.
Follow-up
- Where could label leakage enter this setup?
- What would you monitor after launch to know the model is still valid?
How do you validate model performance beyond simple accuracy metrics?
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
- Where could label leakage enter this setup?
- How would you choose the decision threshold, and who owns that choice?
Describe the process of feature engineering for a time-series forecasting problem.
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?
- Where could label leakage enter this setup?
Can you explain the difference between Decision Trees and Random Forests?
Medium · machine learning and modelling
Approach
- Check what information would not exist at prediction time, and exclude it.
- Say how the offline result would be validated online before it is trusted.
- 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?
Sessionise an event stream with gap and midnight rules
Hard · sessionisation · vectorisation · event streams
Sessionise a raw event stream. Input: a DataFrame with visitor_id, user_id (often NULL), occurred_at_utc and event_name, unsorted, up to 5 million rows. A session breaks when the gap from that visitor's previous event exceeds 30 minutes, and is force-closed at UTC midnight so no session spans two calendar dates. A gap of exactly 30 minutes does not break. Emit one row per session with session_id, visitor_id, the user_id as of the last event in the session, started_at_utc, ended_at_utc, session_date, duration_seconds and event_count. Vectorise; do not loop per visitor.
Approach
- Sort by ['visitor_id', 'occurred_at_utc', 'event_id'] once, then express the whole problem as one boolean vector: a row starts a new session when the visitor changed, or the gap exceeds 30 minutes, or the UTC date differs from the previous row's UTC date. Cumsum that vector and you have the session key.
- Get the comparison direction right on the gap: the rule is strictly greater than 1800 seconds, so an event at exactly 1800 seconds continues the session. Write it as gap > pd.Timedelta(minutes=30), and make the tie a test case rather than an assumption.
- Derive the midnight break from the date change, not from inserting synthetic boundary rows. A date change implies a break even when the gap is two seconds, which is precisely the force-close rule and is why the two conditions are ORed rather than one subsuming the other.
- Aggregate with a single groupby on the session key: min and max of occurred_at_utc, size for event_count, and last for user_id, which is correct because the frame is already sorted so 'last' is the final event in the session. That is the identity-as-of-session-end rule.
- Compute duration_seconds as (max - min).dt.total_seconds(), which makes a single-event session 0 seconds. Say so explicitly, because a downstream mean session duration is sensitive to whether single-event sessions are 0 or excluded.
Worked solution (35 min)
- df = df.sort_values(['visitor_id','occurred_at_utc','event_id']).reset_index(drop=True).
- new_visitor = df.visitor_id.ne(df.visitor_id.shift()); gap = df.occurred_at_utc.diff(); new_day = df.occurred_at_utc.dt.date.ne(df.occurred_at_utc.dt.date.shift()).
- is_start = new_visitor | (gap > Timedelta(minutes=30)) | new_day; df['session_key'] = is_start.cumsum().
- g = df.groupby('session_key'); out = g.agg(visitor_id=('visitor_id','first'), user_id=('user_id','last'), started_at_utc=('occurred_at_utc','min'), ended_at_utc=('occurred_at_utc','max'), event_count=('event_id','size')).
- out['session_date'] = out.started_at_utc.dt.date; out['duration_seconds'] = (out.ended_at_utc - out.started_at_utc).dt.total_seconds(); assign session_id from the sorted index.
Expected result: One row per session where session_count equals is_start.sum(), event_count sums exactly to len(input), every session's started_at and ended_at share one UTC date, and duration_seconds is at least 0 and strictly less than 86400.
Verify it yourself
- out['event_count'].sum() equals len(df); a mismatch means rows were lost to a join or a dropna.
- started_at_utc.dt.date equals ended_at_utc.dt.date on every row.
- For a planted visitor with events at 12:00:00 and 12:30:00 exactly, one session is emitted; move the second to 12:30:01 and two are emitted.
- For a planted visitor with events at 23:59:50 and 00:00:10, two sessions are emitted on consecutive dates despite a 20-second gap.
Follow-up
- Sessions are used as the denominator of a conversion rate. How does moving the inactivity gap from 30 to 45 minutes move that rate, and in which direction?
- A visitor's clock is 40 minutes ahead, so their events arrive with future occurred_at values. What does your sessioniser do, and what would you rather it did?
- The same person signs up mid-session on mobile and continues on desktop. How many sessions and how many users does your output show, and is that the right answer?
Rebuild sessions from raw events with a thirty-minute gap
Medium · window-functions · sessionisation · gaps-and-islands
From fct_event (event_id, visitor_id, occurred_at_utc) alone, rebuild sessions: a new session begins when the gap from that visitor's previous event exceeds 30 minutes, and every session is force-closed at UTC midnight so none spans two calendar dates. Return one row per session with visitor_id, session_start, session_end, event_count and session_date. Do not read fct_session; the point is to reproduce it. Assume duplicate occurred_at_utc values exist for the same visitor.
Approach
- Get the previous timestamp per visitor with LAG(occurred_at_utc) OVER (PARTITION BY visitor_id ORDER BY occurred_at_utc, event_id). The event_id tiebreaker is required, not stylistic: with duplicate timestamps an unstable ordering makes the boundary flags non-deterministic between runs.
- Set a boundary flag when prev IS NULL, or occurred_at_utc - prev > interval '30 minutes', or occurred_at_utc::date <> prev::date. The third disjunct is the midnight rule, expressed as a date change rather than a clock comparison so it holds across any gap length.
- Number the islands with SUM(flag::int) OVER (PARTITION BY visitor_id ORDER BY occurred_at_utc, event_id ROWS UNBOUNDED PRECEDING). Because event_id is unique the ordering is total, so no two rows are peers and RANGE UNBOUNDED PRECEDING would compute exactly the same numbers here. Write ROWS anyway: it is the half of the guard that survives someone later simplifying the ORDER BY back to occurred_at_utc alone, at which point the default RANGE frame gives every row sharing a timestamp one shared running total.
- GROUP BY visitor_id and the island number, then MIN(occurred_at_utc) AS session_start, MAX(...) AS session_end, COUNT(*) AS event_count, session_start::date AS session_date.
- Reconcile against fct_session on one sample day. The counts should agree except for server-emitted events carrying no client session, so a systematic difference beyond those is a bug in the gap rule or in the ordering.
Follow-up
- Why 30 minutes? What does a 5-minute rule do to sessions-per-visitor and to any per-session conversion rate?
- The same person uses phone then laptop. Two visitor_ids, two sessions. What breaks if you sessionise on user_id instead?
- The midnight rule splits an overnight session. Which metrics does that bias, and in which direction?
Find reactivation gaps in account paid-period history
Hard · gaps-and-islands · window-functions · subscriptions
fct_subscription_period holds account_id, subscription_id, period_start_utc, period_end_utc, period_status and change_reason. A mid-period plan or seat change closes one row and opens another, so a single continuous paid tenure is often many rows, and an account may hold two overlapping subscriptions. Collapse rows with period_status in ('active','past_due') into continuous tenures per account, treating gaps of three days or less as continuous. Return account_id, tenure_start, tenure_end, and for every tenure after the first, the gap in days that preceded it.
Approach
- Filter to paid rows only: period_status IN ('active','past_due'). Trialing periods are not tenure, and including them turns every trial that never converted into a one-period tenure followed by a fake churn.
- Order by period_start_utc and take a running maximum of all prior ends: MAX(period_end_utc) OVER (PARTITION BY account_id ORDER BY period_start_utc, period_end_utc, subscription_id ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING). Those three columns are the only stable ordering this schema exposes, so check first that they are unique within an account; if rows tie on all three, the island numbering is order-dependent between runs and you need a real row key before the result is reproducible.
- LAG on its own is wrong here because with overlapping or nested periods the immediately preceding row by start date is not the one that ends latest, so the running maximum is the part that cannot be shortcut.
- Flag a new island when prior_max_end IS NULL OR period_start_utc > prior_max_end + interval '3 days', then number islands with a running SUM of the flag over the same ordering and an explicit ROWS frame.
- Group to (account_id, island) taking MIN(period_start_utc) and MAX(period_end_utc), then LAG(tenure_end) OVER (PARTITION BY account_id ORDER BY tenure_start) to compute the preceding gap in days for every tenure after the first.
- Sanity-check with change_reason, which is the only lineage this schema carries: list its distinct values first, then confirm that rows recording a plan or seat change sit inside a tenure rather than opening one, and that every tenure after the first opens on a row whose reason records a restart rather than an ordinary renewal. Do not reconcile against a churn timestamp on dim_account, which this schema does not define; and where such a column does exist, a cancellation timestamp records when the request was made and routinely sits weeks before the period it ends.
Worked solution (35 min)
- Find an account with a known mid-period upgrade and dump its period rows to use as the trace case.
- Check that (period_start_utc, period_end_utc, subscription_id) is unique per account, since the whole ordering rests on it.
- Write the paid-rows CTE and the running MAX with the explicit frame.
- Add the island flag and the running SUM, then verify the trace account yields one island.
- Group to tenures and add the LAG-based gap in days.
- List the distinct change_reason values, then count accounts with more than one tenure and compare against the count of accounts carrying a restart-flavoured reason anywhere in their history.
Expected result: One row per (account_id, tenure). An account with a continuous history returns a single row with a NULL preceding gap. Every non-first tenure has gap_days strictly greater than 3 by construction.
Verify it yourself
- No two tenures for the same account overlap or touch within the grace window.
- MIN(tenure_start) per account equals MIN(period_start_utc) per account in the filtered input.
- The account with a known mid-period upgrade produces one tenure, not two.
- (period_start_utc, period_end_utc, subscription_id) has no duplicates within an account; if it does, say how many accounts are affected before reading the output.
- The share of accounts with more than one tenure is small; if it is most accounts, the status filter or the grace window is wrong.
Follow-up
- Why three days of grace? What do 0 and 30 days each do to the count of accounts classed as reactivated?
- An account runs two concurrent subscriptions for different teams. One tenure or two, and what does the revenue reader expect?
- How would you turn these tenures into a monthly gross logo churn series without double-counting an account that churned and returned in the same month?
How would you design a pipeline for a business knowledge base?
Medium
Approach
- State your assumptions explicitly before working the problem.
- Clarify what is being asked and what a complete answer would contain.
- Say what you would check first and why it is the highest-information step.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
A completion rate the owning team can move without fixing anything
Medium · gaming · instrumentation · funnel
A team's target is core-flow completion rate: distinct fct_event.flow_instance_id with a 'flow_completed' event within 30 minutes of its 'flow_started' and no 'error_shown' carrying the same flow_instance_id in between, over distinct flow_instance_id with a 'flow_started' in the window, split by surface and app_version. The same team owns the client that emits those events and the tracking plan that defines them. List the ways this rate rises without any user completing more flows, then redefine the metric and its guardrails so those routes are closed. Deliverable: the hardened definition.
Approach
- Work the emission side first, because that is what the team controls: delay minting flow_started until after the first screen so the highest-dropping attempts leave the denominator; stop emitting or rename error_shown; mint a fresh flow_instance_id on each retry so one failed attempt becomes several attempts whose last one completes; move flow_completed earlier in the flow.
- Sort those moves by where they are visible. None of them shows in the rate itself; three of them show only in volume, which is why the denominator has to be published on the same chart as the rate.
- Re-anchor the numerator on something outside the flow's own instrumentation: require a downstream is_core_action = TRUE event for the same user_id within 30 minutes of flow_completed, so a completion only counts when it produced the thing the flow exists to produce.
- Add the guardrail that catches the retry route specifically: mean and p90 flow_instance_id per user per day, with the rule written down that a rising completion rate alongside rising attempts per user is a regression and not a win.
- Make definition changes visible instead of forbidden: stamp a tracking-plan version on the series and re-base the history when event semantics or the 30-minute window change, rather than splicing two definitions into one line.
Worked solution (25 min)
- Enumerate at least four emission-side moves and mark for each whether it shows in the rate or only in volume.
- Pin the denominator: publish flow_started per 1,000 sessions and per distinct user beside the rate.
- Rewrite the numerator to require a downstream core action for the same user within 30 minutes.
- Add the attempts-per-user guardrail with its interpretation rule stated as a sentence.
- Version the definition and write the re-base policy for the historical series.
Expected result: A hardened definition in which each enumerated gaming route is either blocked by the numerator, exposed by a published volume series, or forced to surface as a definition version change.
Verify it yourself
- Replay one untouched historical week under the old and hardened definitions. A large gap means the hardening changed the meaning of the metric, not only the incentives around it.
- flow_started per 1,000 sessions should be roughly continuous across an app_version boundary unless the flow's entry point genuinely moved; a step change there is an instrumentation change.
- Attempts per user and completion rate should not rise together within one build.
Follow-up
- How do you distinguish a genuine instrumentation fix from gaming, given both look like a step change confined to one release?
- The jump appears in exactly one app_version. Does that exonerate the team or implicate it?
- What do you do with eighteen months of history once the definition is hardened?
Tell a pipeline outage from a collapse in usage
Medium · pipeline-failure · instrumentation · event-volume
Weekly active accounts completing a core action fell 9%, and almost the entire fall sits in accounts whose events carry surface = 'ios'. App crash rates and store reviews are unchanged. You have fct_event with occurred_at_utc, received_at_utc, event_name, is_core_action, app_version and surface, plus fct_session and the ingestion job run log. Establish within the hour whether iOS engagement fell or iOS events stopped arriving, name the evidence that distinguishes them, and say what you would publish on the dashboard in the meantime.
Approach
- Compare the event-name composition inside surface = 'ios' against the prior four weeks as shares, not counts. A behaviour collapse scales most event names together; a dropped event definition or a broken downstream filter hits specific event_name values while page_view and session-opening events hold steady. That shape difference is the fastest discriminator available.
- Profile the received_at_utc minus occurred_at_utc distribution per day for surface = 'ios'. A stalled-then-backfilling pipeline shows a fat upper tail and a recovering p99; a silently dropped stream shows an unchanged lag distribution over a smaller volume. The two failure modes have different remedies and different histories.
- Cut by app_version. A logging SDK change arrives with one build and ramps with its adoption curve; an infrastructure fault arrives across every build within the same hour. Checking this costs one group-by and rules out half the hypothesis space.
- Cross-check against a signal that does not travel the suspect path: server-emitted events with session_id NULL, and subscription or billing activity for the same accounts. If those accounts are still transacting, the users did not leave.
- Publish an ex-iOS total with an explicit annotated break rather than a blended total. A blended number during a known ingestion gap is wrong in a direction you can already name, and republishing it daily spreads the artefact into every downstream report.
Follow-up
- Suppose the events do eventually backfill. What is your policy for restating the published weekly numbers, and who needs to be told?
- What monitor would have caught this before a human noticed the weekly metric, and what would it alert on?
- If is_core_action is maintained in the tracking plan, what governance would stop a change to that list from silently moving a north-star metric?
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.
What the behavioural round is actually scoring
Interviewers here are not checking whether you can describe a project. They want the decision you made, why you made it under the information you had, and what changed afterwards that someone else could measure. A story that ends at 'I built a model' has no ending. Say what the model caused, or what you stopped doing because of it.
How do you handle imbalanced datasets in predictive modeling?
Medium · behavioural and stakeholder questions
Approach
- Name the disagreement or constraint, and how you resolved it with evidence.
- 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.
Follow-up
- How did you know the outcome was caused by your change?
- What would you do differently if you ran that project again?
Quantify your own impact without claiming the topline you touched
Hard · self-assessment · attribution · communication
You are writing the impact section of your own review. Over the year you ran four experiments, one of which shipped and three of which were flat; you corrected the definition of gross monthly revenue churn so that cancellation is recognised at period_end_utc; and you built a self-serve funnel dashboard. Weekly active accounts rose 14% over the same period. Your reviewer knows the data well. Write the three impact claims you would defend, stating for each what you contributed, what evidence supports it, and what portion of the outcome you are not claiming.
Approach
- Recognise what is being probed: whether you apply to your own work the causal standard you would apply to somebody else's roadmap claim. Nearly everyone who would reject 'accounts that do Y retain better' will write 'I drove a 14% increase' without noticing it is the same error with a friendlier subject.
- Sort the work by the kind of evidence it can carry. The shipped experiment is the only item with a randomised estimate, so it is the only one where an effect size is defensible, and you claim the interval rather than the point estimate.
- Claim the three flat experiments as decisions prevented and price them. Features not built, or built differently, on evidence, with the engineering weeks reallocated as the number somebody else can verify. A defensible null is a delivered decision and should be written as one.
- Claim the definition fix as correctness, not as improvement. The old figure was overstated by a specific percentage and appeared in a specific set of recurring documents; the impact is the change it produced in the forecast built on top of it, not a change in churn itself.
- Claim the dashboard on usage and displacement: distinct weekly users of it, and the ad-hoc request count for six months before against six months after. If the request log does not exist, record the claim as unverified rather than estimating it upward.
- Disclaim the 14% explicitly and once. State that it cannot be separated from seasonality, other teams' launches and a pricing change, and bound your own contribution from above using the shipped experiment's interval converted into headline units.
Follow-up
- Your shipped experiment's interval was +0.2pp to +1.4pp on activation. How much of the 14% can that account for, and how do you say so without undercutting yourself?
- A peer in the same cycle claims the full 14%. What, if anything, do you do about it?
- If you could only keep two of your three claims, which do you drop, and why that one?
Walk through an analysis you got wrong and what changed
Easy · postmortem · data quality · self-assessment
Describe an analysis of yours that turned out to be wrong after somebody had already acted on it. You have four minutes. The account must name the defect mechanically, the join, the filter, the window or the identity key, rather than describing it as a communication problem. It must also say who did what because of the wrong number, how the error surfaced, how long it stood, and what control you put in place so that class of error cannot reach a decision again. Do not pick an error nobody acted on.
Approach
- Recognise what is being probed: whether you can be specific about your own failure without minimising it or performing contrition. The discriminator is whether the defect has a mechanism the listener could reproduce in their own warehouse.
- Choose the case by blast radius rather than by comfort. An error nobody acted on tests nothing, and picking one signals that you are managing the interview instead of answering it.
- Structure the account in six beats: the number, the decision it drove, the defect, the detection, the correction, the control. Keep the defect to one reproducible sentence, for example an inner join to fct_subscription_period that dropped accounts with no subscription row and so computed retention over payers only.
- State the direction of the bias, not only its existence. A filter or join that removes rows usually moves a metric predictably, and knowing which way shows you diagnosed the mechanism rather than patched the symptom.
- Be exact about detection and elapsed time. 'A colleague noticed' and 'the row-count assertion failed before publication' are different answers about the same organisation, and the second one is the one your control is supposed to produce next time.
- End on the control, its cost, whether it has fired since, and one thing it does not cover.
Follow-up
- What did the control cost, and has it fired since? If it never has, how do you know it works?
- How long did the wrong number stand before anyone questioned it, and what does that say about the review path it went through?
- What is the equivalent mistake you are most likely to make in this role, given the tables you would be working in?