Interview guides · Adentis Portugal
Adentis Portugal Data Scientist Interview Questions & Guide 2026
Diagnose a metric move for Adentis Portugal data scientist prep: split by segment, rule out a logging change, separate mix shift from behaviour.
The 60-second brief
As a Data Scientist at Adentis Portugal, you play a pivotal role in transforming data into actionable insights that drive business decisions and enhance user experiences. This position is integral to the company’s mission of delivering innovative solutions tailored to client needs. Your work will directly influence the development of products and services, ensuring they meet the highest standards of quality and relevance in the market.
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
- Strip CI, retry and synthetic traffic first
- Power experiments for heavy-tailed account revenue
- Analyse at the account grain, cluster errors
Overview
In this role, you will engage with complex datasets, applying statistical analysis and machine learning techniques to uncover trends and patterns. You will collaborate with diverse teams, including engineering and product management, to ensure data-driven decision-making aligns with strategic objectives. Working on real-world problems, you will have the opportunity to contribute to projects that impact industries ranging from finance to healthcare, making your role both challenging and rewarding.
The Adentis Portugal interview process
Adentis Portugal candidates report 5 rounds · ≈ 4-6 weeks. The stages below are what candidates describe, not a published process.
Initial Phone Screen
Most candidates lose this call inside the first two minutes, during the walkthrough of their own background. The account runs chronologically, sits at the level of tools and titles, and never arrives at a decision anyone could have disagreed with. Anchor on a problem instead of a timeline: what the team could not answer, what you did about it, what happened next. Ninety seconds is enough, and stopping on time leaves room for the half of the call that belongs to you. What you ask about how work gets prioritised signals your level more reliably than the walkthrough does.
What to demonstrate
- Whether your background summary has a shape (problem, decision, consequence) or is a chronological list of tools and employers
- Whether you can account for gaps, short stints and the reason you are looking, unprompted and without hedging
- The substance of the questions you ask back, which an experienced screener reads as a level signal
How to prepare
- Time your opening walkthrough against a clock. If it runs past two minutes, compress the earliest role into a single clause and spend the recovered time on the most recent one
- Write one honest sentence for every gap or short stint visible on your resume and offer it before being asked about it
- Prepare questions about how work arrives and gets prioritised: who writes the request, how often priorities change, and what happens to an analysis after it is delivered
In-Depth Interviews
An extra round usually exists because something is still open after the standard loop: a skill the earlier interviews did not sample, a level decision, or two interviewers who disagreed. It is rarely a rerun of what you already did well. Ask the recruiter who you are meeting, what function they sit in, and how long the session runs. That is an ordinary scheduling question, and the answer changes what you should prepare. What separates a strong candidate here is treating the round as a fresh evaluation with its own bar, rather than assuming earlier performance carries you through or sinks you.
What to demonstrate
- Whether you can answer well on ground the earlier rounds did not cover, without leaning on what you already said to someone else
- Consistency of the facts in your stories: the same sample size, timeframe, team size and scope of your own role as in earlier conversations
- How you handle an unfamiliar format live, including whether you ask what kind of answer is wanted before producing one
How to prepare
- Ask the recruiter for the interviewer's function, the length, and whether to expect a coding surface, a discussion, or a presentation. Preparing for a 30 minute conversation with a partner team is not the same work as preparing for a 60 minute technical block.
- Write out what each earlier round actually covered, then list the two or three areas nobody probed. That gap is the most likely subject of the extra round.
- Re-read the numbers in the project stories you have already told, so a second telling does not quietly contradict the first.
Technical Assessments
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
Behavioral Questions
Most of the weight in this round sits on the disagreement questions. Data work routinely produces an answer someone senior did not want, and the interviewer is trying to learn what you do in that hour. Both failure modes are common: folding as soon as a director pushes back, and treating the pushback as ignorance to be corrected with a better chart. A strong answer usually contains a specific thing the other person knew that you did not, and describes how you found out whether it changed the conclusion.
What to demonstrate
- Whether you can state the other side's argument accurately before you explain why you disagreed
- What you treated as evidence during the disagreement, such as a rerun under their assumption or a holdout check, rather than persuasion technique
- Whether you distinguish being overruled from being wrong, and can give an example of each
How to prepare
- Write out one disagreement where you turned out to be wrong, and say what in the data misled you. Candidates prepare the story where they were right, and the follow-up asks for the other one.
- For your main disagreement story, be ready to say what result would have made you drop your position. If no such result exists, you were not arguing from the data.
- Practise stating the opposing position out loud in one sentence the stakeholder would accept, then continue the story.
Final Interviews
Where a loop includes a partner from outside the data team, that conversation usually carries the same weight as the technical ones and gets the least preparation. The person opposite you will not follow a derivation and does not need to. They are working out whether having you involved would make their decisions better or slower. The failure mode is not being too technical. It is answering a question about a decision with a description of your method, leaving the translation to them. What they carry into the debrief is the sentence you handed them, not the analysis underneath it.
What to demonstrate
- Whether a statistical result arrives as something the partner could act on, with the one caveat that would change their decision kept and the rest left out
- Whether you can state what you need from their side, in their terms: instrumentation that does not exist yet, a definition they own, or a holdout they have to agree to
- Whether uncertainty is given as a range someone can plan against, rather than as hedging that invites them to ignore the result
- Whether you ask what decision is actually on the table before explaining anything
How to prepare
- Take a result you know well and write the version for someone who stops reading after one sentence, then the three-minute version, and check the short one is not the long one with the qualifications stripped out
- For a past project, list everything you asked a non-technical partner for and how you phrased it, then rewrite each ask so it names what goes unmeasured without it
- Practise saying where a result does not apply, out loud, in one sentence that a partner could repeat accurately to someone else
Where candidates lose points
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.
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.
Over-explaining the method and under-explaining the implication
Lead with the answer and what you would do about it, then give the approach when asked. Roughly one sentence of method per three of implication is the right ratio for a stakeholder-facing answer; the interviewer already knows what a regression is.
Writing SQL without stating NULL and tie-breaking behaviour
Before calling a query finished, say what it does with NULLs, ties and empty groups. NOT IN against a subquery containing a single NULL returns no rows at all, and RANK, DENSE_RANK and ROW_NUMBER differ precisely on ties, so name which one the question requires.
Questions & practice
Write a function to calculate the mean and standard deviation of a list of numbers.
Medium · statistics and probability
Approach
- Translate the result into the decision it informs, in one plain sentence.
- Say what the estimate is of, and over what population it generalises.
- Sanity-check the answer against a simple bound or a simulated case.
Follow-up
- Which assumption here is most likely to be violated in practice?
- What sample size would you need to detect an effect half this size?
What machine learning algorithms are you most familiar with, and when would you use each?
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.
- 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?
How would you implement a decision tree from scratch?
Medium · machine learning and modelling
Approach
- Set a baseline first, so any model has something honest to beat.
- Check what information would not exist at prediction time, and exclude it.
- Frame the prediction: the label, the moment of prediction, and the action it triggers.
Follow-up
- How would you choose the decision threshold, and who owns that choice?
- Where could label leakage enter this setup?
Quantify billable volume created by retries after server errors
Hard · telemetry-hygiene · pandas · unit-economics
From fct_api_request (request_id, account_id, endpoint, idempotency_key, is_retry, http_status, request_at, billable_units), measure how much billable volume in a 28-day window is retry traffic that followed a 5xx. Group requests into attempt chains by (account_id, endpoint, idempotency_key) ordered by request_at; a request is error-driven if any earlier attempt in its chain returned 5xx. Requests with a null idempotency_key cannot be chained, so report them as their own class rather than assuming each is unique. Return billable_units split into first-attempt, error-driven retry, other retry and unchainable, per account.
Approach
- Split the population before measuring anything. A null idempotency_key is not a chain of one, it is an unknown; report its share of billable_units first, because if it is 40% of volume then the headline estimate is a lower bound and the deliverable has to say so.
- Within chainable rows, sort by (account_id, endpoint, idempotency_key, request_at) and derive 'any earlier attempt failed' with arithmetic rather than a per-group lambda: with is5 = (http_status >= 500), the per-chain cumsum minus the row's own value is positive exactly when an earlier attempt in that chain returned 5xx. A groupby-apply gives the same answer and is unusable at five million rows.
- Do not take is_retry as the definition. It is set by the client whenever an idempotency_key is resent, which covers retries after client-side timeouts and after 4xx as well; compute the flag yourself and then cross-tabulate it against is_retry, because the disagreement is a finding in its own right.
- Aggregate billable_units by (account_id, class) and assert the classes sum to each account's total. The spine sets billable_units to zero on 5xx responses, so the failed attempt contributes nothing and the whole inflation sits in the successful retry that follows it.
- Report the per-account share and look at its distribution, not the fleet total. One account in a retry storm dominates any blended figure, which is the same failure that makes a fleet-wide error rate useless.
Worked solution (40 min)
- unchainable = df.idempotency_key.isna(); report df.loc[unchainable].groupby('account_id').billable_units.sum() before proceeding.
- keys = ['account_id','endpoint','idempotency_key']; c = df[~unchainable].sort_values(keys + ['request_at'], kind='mergesort'); c['is5'] = (c.http_status >= 500).astype(int)
- c['attempt_no'] = c.groupby(keys, sort=False).cumcount(); c['prior_5xx'] = (c.groupby(keys, sort=False).is5.cumsum() - c.is5) > 0
- c['cls'] = np.where(c.attempt_no == 0, 'first_attempt', np.where(c.prior_5xx, 'error_driven_retry', 'other_retry')); out = pd.concat([c, df[unchainable].assign(cls='unchainable')]).groupby(['account_id','cls']).billable_units.sum().unstack(fill_value=0)
Expected result: A per-account table of four columns summing exactly to that account's total billable_units in the window. error_driven_retry is zero for any account with no 5xx; the fleet-level error-driven share is typically low single digits while a small number of accounts sit far above it.
Verify it yourself
- out.sum(axis=1) equals df.groupby('account_id').billable_units.sum() for every account.
- Every row classed error_driven_retry has attempt_no >= 1.
- An account with no http_status >= 500 in the window has error_driven_retry == 0.
- Cross-tabulating cls against is_retry: no first_attempt row should carry is_retry True, and any that do mark chains truncated by the window boundary.
Follow-up
- An account's error-driven share is 22%. Is that the platform's fault or the client's, and what do you look at next?
- How would you define a consumption-based north-star metric that an outage cannot inflate?
- Chains straddle the 28-day boundary. How large is that bias and in which direction?
Find paid seats that never called the API
Easy · anti-join · null semantics · seat utilisation
dim_user_membership carries user_id, account_id, seat_type, is_service_account, deactivated_at and last_seen_at. fct_api_request carries user_id, which is null whenever the caller is a service account or an unattended key, plus account_id, request_at and http_status. For a single account, list every licensed_paid seat with is_service_account = false and deactivated_at null that issued no request at all in the trailing 90 days, returning user_id and last_seen_at. A first draft writes NOT IN against a subquery over fct_api_request.user_id. State exactly what that draft returns and why, then write the correct query.
Approach
- Read the column comment first: user_id is nullable on the fact, so the subquery almost certainly contains at least one NULL for any account that runs a service account or an unattended key.
- Work the three-valued logic out loud. x NOT IN (a, NULL) expands to NOT (x = a OR x = NULL); the second disjunct is UNKNOWN, so for any x not equal to a the whole predicate is UNKNOWN and the row is filtered out. The draft returns zero rows, which reads as full seat utilisation.
- Rewrite as NOT EXISTS with the 90-day and status predicates inside the correlated subquery. Putting them in the outer WHERE instead turns the anti-join into a different question and silently changes the answer.
- Correlate on both user_id and account_id. A membership is (user_id, account_id) and one person can hold memberships in several accounts, so correlating on user_id alone marks a seat as active because that human was busy somewhere else.
- Filter the seat side to seat_type = 'licensed_paid', is_service_account = false and deactivated_at IS NULL, and say what the resulting count means next to contracted_seats on the current subscription row.
Worked solution (15 min)
- Confirm the hazard with one query: SELECT count(*) FROM fct_api_request WHERE user_id IS NULL AND account_id = :account_id. Any non-zero result proves the draft returns nothing.
- Write the seat side: SELECT user_id, last_seen_at FROM dim_user_membership WHERE account_id = :account_id AND seat_type = 'licensed_paid' AND NOT is_service_account AND deactivated_at IS NULL.
- Attach AND NOT EXISTS (SELECT 1 FROM fct_api_request r WHERE r.user_id = m.user_id AND r.account_id = m.account_id AND r.request_at >= now() - interval '90 days').
- Compare the row count against the same query written as a LEFT JOIN with a WHERE r.user_id IS NULL; the two must agree exactly.
- Divide the active seat count by contracted_seats from the account's current fct_subscription_period row and state the utilisation figure.
Expected result: A list of user_id and last_seen_at for licensed, non-service, active memberships on that account with no request in 90 days. The NOT IN draft returns zero rows whenever the account has any service-account or unattended-key traffic, and returns the right answer only by accident on accounts that have none.
Verify it yourself
- The NOT EXISTS result and the LEFT JOIN ... IS NULL result return identical user_id sets.
- Idle seats plus seats with at least one request in 90 days equals the total licensed non-service active membership count for the account.
- Forcing a NULL into the subquery (union a SELECT NULL) leaves NOT EXISTS unchanged and empties the NOT IN version, which demonstrates the failure on demand.
Follow-up
- Adding AND user_id IS NOT NULL to the subquery also fixes NOT IN. Why is NOT EXISTS still the form you would leave in the repository?
- last_seen_at looks like a shortcut for the whole query. What does it actually record, and where does it disagree with API activity?
- This is a seat-reduction risk list. What threshold would you attach before handing it to an account team, and what happens to seats below it?
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
- 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.
- 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.
- 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.
- Aggregate by date_trunc('week', created_at AT TIME ZONE 'UTC'), counting accounts and activated accounts, and divide as a ratio of counts.
- 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?
How would you prioritize tasks when faced with multiple deadlines?
Medium · product sense and metrics
Approach
- Name one primary metric, then the guardrail that stops it being gamed.
- 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
- How would you detect that the metric is being gamed rather than genuinely improving?
- Which segment would you cut first, and what would that rule out?
Given a dataset, how would you approach identifying trends?
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.
- State what result would change your recommendation, so the answer is falsifiable.
Follow-up
- How would you detect that the metric is being gamed rather than genuinely improving?
- What would you do if the primary metric and the guardrail moved in opposite directions?
How do you stay current with industry trends and technologies?
Medium · product sense and metrics
Approach
- Decompose the metric into the rates that drive it, and say which one you would check first.
- State what result would change your recommendation, so the answer is falsifiable.
- Restate the decision this analysis has to support, and who acts on the answer.
Follow-up
- Which segment would you cut first, and what would that rule out?
- How would you detect that the metric is being gamed rather than genuinely improving?
What tools do you use for data visualization, and why?
Medium
Approach
- Say what you would check first and why it is the highest-information step.
- Work from the decision backwards to the evidence you would need.
- Clarify what is being asked and what a complete answer would contain.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
Pick the randomisation unit when workspaces and queues are shared
Medium · interference · randomisation-unit · clustering · switchback
A collaboration feature was tested by randomising 18,000 users 50/50. dim_user_membership shows a median of 9 activated, non-service users per paying account, and weekly successful interactive requests per user carries an intra-class correlation of 0.25 within an account. The reported lift is 4% at p = 0.05. Separately, the same team wants to evaluate a change to the admission-control queue, which is shared by every account in a region. Give the correct randomisation unit for each test, the effective sample size and corrected p-value for the first, and the design for the second.
Approach
- Separate the two failures of user-level randomisation, because they need different fixes and only one of them is a variance problem. Interference: a treated colleague changes a control colleague's behaviour inside a shared workspace, which biases the estimate toward zero. Correlated outcomes: users inside an account are not independent draws, which understates the variance. Clustering the standard errors fixes the second and leaves the first entirely intact.
- Compute the design effect: 1 + (m - 1) * rho = 1 + 8 * 0.25 = 3.0. Effective sample is 18,000 / 3 = 6,000 users, corresponding to roughly 2,000 accounts. Standard errors are understated by sqrt(3) = 1.73, so a reported t of 1.96 is really 1.13 and the honest two-sided p is about 0.26, not 0.05.
- Re-run the first test randomised on account_id with inference clustered on account_id, or equivalently as a regression on account-level means. The account-means version is the conservative one and is usually easier to defend to a non-specialist audience, at the cost of some efficiency when cluster sizes are very unequal.
- For the queue change, account randomisation fails too, and for a different reason: treated and control accounts contend for the same finite capacity, so the control arm is mechanically affected by the treated arm and the contrast estimates a property of a mixed system rather than the effect of the policy. Randomise time instead, with a switchback alternating the admission policy at the region-by-30-minute-slot level.
- Specify the switchback so it is actually valid. Impose a washout at the start of each slot at least as long as the p99 queue drain time and discard requests enqueued before the switch; randomise slot order rather than strictly alternating, because a fixed alternation aliases with the hourly and weekday traffic cycle; block on hour-of-day so both policies see peak and trough; and cluster inference on the slot, which is the unit that was randomised.
- State the power consequence plainly: the cluster count is slots, not requests. Fourteen days of 30-minute slots gives 672 slots, and the variance to plan against is between-slot variance in the outcome, which is far larger than between-request variance and is what makes switchbacks expensive.
Worked solution (30 min)
- Compute the design effect 1 + (9 - 1) * 0.25 = 3.0 and the effective sample 18,000 / 3 = 6,000 users, about 2,000 accounts.
- Inflate the standard error by sqrt(3) = 1.732, convert the reported t of 1.96 to 1.13, and read off a two-sided p of about 0.26.
- Recompute the same contrast on account-level means as an independent check that does not depend on the pilot's rho estimate.
- Lay out the switchback: region-by-30-minute slots, randomised order, washout equal to the p99 drain time, blocking on hour-of-day, inference clustered on slot, 672 slots over fourteen days.
Expected result: Design effect 3.0, effective sample about 6,000 users or 2,000 accounts, corrected two-sided p about 0.26. The 4% lift is not significant once clustering is respected and is additionally biased toward zero by within-workspace spillover. The queue change is run as a randomised-order switchback with slot-level clustering and a drain-time washout, not as an account-level split.
Verify it yourself
- The design effect depends only on cluster size and intra-class correlation: it must collapse to 1 when rho = 0 and when m = 1. If your expression does not, it is not the design effect.
- An account-means regression and a user-level regression with cluster-robust standard errors should give similar standard errors. A large gap indicates very unequal cluster sizes, in which case the means version needs weighting.
- The switchback's sample size is slots, not requests. If your power calculation cites a sample in the millions, you have counted requests and will badly overstate precision.
Follow-up
- The intra-class correlation was estimated at 0.25 from a 300-account pilot. If it is really 0.40 the design effect becomes 4.2. How do you plan under that uncertainty rather than betting on the point estimate?
- Under what conditions is user-level randomisation still the right choice even though users share accounts?
- The queue change is expected to leave a four-hour retry backlog. What does carryover of that length do to a 30-minute switchback, and what would you run instead?
Activation drops six points starting at a deploy hour
Medium · instrumentation · release analysis · metric inputs
Seven-day activation, defined as an account's first request with http_status < 400, api_key_id not null and traffic_class <> 'synthetic_monitor' within 168 hours of created_at, fell six points for sign-up cohorts after a Tuesday. A client SDK major version shipped that morning. From fct_api_request (account_id, api_key_id, sdk_name, sdk_version, http_status, traffic_class, request_at) and dim_account (account_id, created_at, is_internal), decide whether activation actually fell or the metric's inputs changed, and state in advance what evidence would convince you of each.
Approach
- Decompose the definition and recompute activation under each relaxation: status only, status plus traffic_class, then the full definition. If the entire drop lives in the api_key_id clause, this is an instrumentation question rather than a behavioural one.
- Measure the null rate of api_key_id by sdk_version and by hour. A stamping change appears as a step at the deploy boundary confined to the new version; a behavioural change appears as a ramp that grows with adoption and leaves old-version traffic untouched.
- Hold the cohort's SDK mix fixed before comparing. New sign-ups adopt the newest version first, so a cohort-level drop can be pure composition even when no individual version changed at all.
- Corroborate with a source the release did not touch: whether the same cohorts appear in fct_usage_daily with non-zero billable_quantity, and whether their fct_support_ticket rows with category = 'onboarding' rose.
- Write the decision rule down before looking at the answer. An instrumentation artefact predicts unchanged downstream usage and a version-confined null step; a real regression predicts falling downstream usage and more onboarding tickets in the same cohorts.
Follow-up
- Old-version and new-version populations are not exchangeable, because new accounts adopt the new version first. How would you build a comparison that is not confounded by cohort age?
- What backfill or metric-versioning policy keeps the historical series interpretable once you fix the stamping?
Suggested practice checklist
Instead of guessing where the week should go, day one measures it under a fixed rubric and allocates the remaining hours in proportion to the gaps. The method is deliberately rigid: the allocation is written down before any studying starts and is not renegotiated when a topic turns out to be unpleasant.
Day 1 — Diagnostic, scored before you study anything
- Sit a 100-minute timed diagnostic in four blocks: 30 minutes of SQL across three prompts, 25 minutes of short-answer statistics, 25 minutes on one modelling or case prompt, and 20 minutes delivering one behavioural story aloud.
- Score each block from 0 to 3 on a fixed rubric where 3 is correct and fluent, 2 is correct but slow or prompted, 1 is partially correct, and 0 is stuck, grading the output rather than how the attempt felt.
- Allocate the hours for days two to five roughly in proportion to 3 minus the score in each block, write the allocation down, and commit to not revising it midweek.
Deliverable: A scored rubric and a fixed hour allocation for the rest of the week.
Day 2 — Largest gap: find the boundary rather than the subject
- Break the weakest area into five named sub-skills (for query work: grain control, window frames, date arithmetic, set logic with NULLs, and reading a query plan) and rate each one, so the rest of the week targets a sub-skill instead of a subject.
- Solve three problems chosen to sit just above where the rating drops off, and for each write the first move you failed to make.
- Re-solve one of them from memory four hours later, on paper, with nothing open.
Deliverable: A five-item sub-skill map with the two blocking sub-skills circled.
Day 3 — Largest gap: drill the blocking sub-skill
- Do eight short repetitions of the same shape rather than eight different problems, so what you practise is the pattern and not the puzzle.
- Write the rule you now hold in one sentence, then test it against a case built to break it: a ranking function over a column with ties, or a two-sample test on observations that are obviously dependent.
- Have someone else read your one-sentence rule and find the precondition you left out.
Deliverable: One rule statement with its preconditions attached and one counterexample that would have caught the incomplete version.
Day 4 — Second gap, plus maintenance on your strongest area
- Run the same sub-skill map and boundary protocol on the second-largest gap, compressed into half the day.
- Spend 25 timed minutes on your strongest area to stop it decaying, choosing the hardest problem you can still finish rather than an easy warm-up.
- Compare how the two areas fail: whether you lose time on recall, on setup, or on arithmetic, because the fix differs for each.
Deliverable: A second sub-skill map plus a one-line diagnosis of how each area fails you.
Day 5 — The gap that is not a skill
- Record yourself answering one technical and one behavioural prompt, then count two things in the playback: how many seconds before your first clarifying question, and how many sentences you started without knowing where they ended.
- Rewrite your three most-used stock phrases into shorter versions, and practise saying "I do not know, here is how I would find out" without softening it into a guess.
- Deliver one answer again with a hard 90-second limit to force structure before detail.
Deliverable: Two recordings with a counted improvement in time-to-first-question.
Day 6 — Retest under day-one conditions
- Sit the same 100-minute diagnostic structure with new prompts of comparable difficulty and score it on the identical rubric.
- Compare block by block, and for any block that did not move, change the method rather than adding hours: a block stuck at 1 usually means the practice was too varied, not too short.
- Write which single block you would still lose the offer on.
Deliverable: A second scored rubric placed next to the first, with one named remaining risk.
Day 7 — Full loop under interview conditions
- Run a 60-minute mock covering the two blocks that moved least, with an interviewer instructed to interrupt and change direction.
- Write your recovery script for the moment you go blank: restate the question, state your assumption, name the first thing you would check.
- Reduce the week to the rule statements you wrote, each with its preconditions attached, then say every one of them out loud without reading it and cut any you cannot state in a single sentence, since a rule you have to reconstruct mid-answer will not survive being interrupted.
Deliverable: A one-page card holding the recovery script and only the rules you could state from memory.
Ownership when the result is inconvenient
Sometimes the honest read is that the initiative did not work, and the person who commissioned the analysis was hoping otherwise. Interviewers want to know whether you softened it. Prepare the case where you delivered an unwelcome result, how you presented the uncertainty without hiding behind it, and what the team did next.
How do you handle feedback and criticism in a professional setting?
Medium · behavioural and stakeholder questions
Approach
- Pick a story where you drove the decision, not one where you observed it.
- Name the disagreement or constraint, and how you resolved it with evidence.
- 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 did you decide not to do, and why?
How do you handle missing data in a dataset?
Medium · behavioural and stakeholder questions
Approach
- Close with what you would do differently, concretely.
- Name the disagreement or constraint, and how you resolved it with evidence.
- 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 did you decide not to do, and why?
Allocate one analyst week across three competing requests
Easy · prioritisation · stakeholders · scoping
Three requests arrive the same morning and you have one week. Finance wants per-account gross margin from fct_usage_daily for a pricing review in three weeks. Sales wants a renewal-risk list for accounts with term_end_date inside 60 days. A product manager wants an experiment readout for a decision being taken on Thursday. Produce your allocation with hours attached, what you say to whoever receives less, and one thing you refuse to do this week, with the reason each decision is defensible to the person it costs.
Approach
- The interviewer is probing whether you prioritise on decision timing and reversibility or on who asked most forcefully. Sort by the date each decision is actually taken and by what the default outcome is if nothing arrives.
- Apply that sort concretely. The Thursday readout has a hard irreversible deadline and no value afterwards. The pricing review has three weeks of slack. The renewal list has a rolling deadline set by term_end_date, so part of it is urgent this week and the rest is not, which means it can be split rather than deferred whole.
- Find the cheapest sufficient version of each request rather than the full version. The readout goes in full. The renewal list ships as a filtered query over renewal-eligible accounts ranked by two inspectable signals rather than as a model. The margin work is scoped to the accounts that dominate the pricing decision, since revenue is heavily skewed and the tail will not change the conclusion.
- Make the trade visible in one written note to all three at once, with dates. Telling each person separately that they are the priority is how an allocation becomes a credibility problem.
- Refuse something explicitly and say why. The model version of the renewal list is the usual candidate, because it cannot be evaluated without a holdout nobody has agreed to yet, and building it this week forecloses that.
- Leave slack. A plan with none is a plan to miss the one deadline that cannot move.
Follow-up
- The sales leader escalates to your manager. What did you already do that makes that a short conversation?
- Which of the three deadlines would you push back on, and what exactly would you ask for?
- What would you change about how these requests reach you so next week is not the same?