At SFL Scientific, a Data Scientist operates at the intersection of cutting-edge research and commercial application. As an elite data science consulting firm, the company helps complex organizations—ranging from startups to Fortune 100 enterprises—solve their most challenging business problems using artificial intelligence, machine learning, and advanced analytics. Unlike traditional product-focused roles, your work here spans multiple industries, requiring you to adapt rapidly to diverse data types, including image, text, and audio pipelines.
You will be responsible for translating ambiguous client requirements into robust, production-grade machine learning systems. The impact of this role is direct and highly visible, as your solutions frequently dictate the strategic direction of high-value client contracts. This requires not only deep technical expertise but also the professional maturity to present complex mathematical concepts to stakeholders who may not have a technical background.
Working alongside a team largely comprised of PhD-level researchers, you will tackle unstructured data challenges that push the boundaries of standard machine learning. Whether you are building an audio classification pipeline or designing an object detection system, your contributions will directly influence the technical capabilities and reputation of in the consulting market.
Initial Screening Call
reportedA screening call is a matching exercise run by someone who will not evaluate your statistics. They are checking that the work described on your resume is work you personally did, and that its scope matches the level the role is written for. Logistics get settled in the same half hour so nobody spends an interviewer's afternoon on a mismatch. The answer that fails is the one narrated in the plural. If every sentence is 'we built' and 'the team decided', there is nothing specific to write down about you. Name the piece that was yours, the decision you made inside it, and what changed after.
What to demonstrate
- Whether the ownership implied by your resume survives one round of follow-up about who actually did which part
- Whether your described scope (data size, stakeholders, what shipped) matches the seniority the role is written at
- Whether timeline, location and compensation expectations make the rest of the loop worth scheduling
How to prepare
- Rewrite your top three resume bullets in the first person singular, each with the decision you made and what moved afterwards, then say them out loud once so the 'we' does not return under pressure
- Attach one number to each project: the baseline, the change, and the window it was measured over. Where impact was never measured, say that plainly rather than inventing a figure
- Settle your compensation range before the call and give it as a range with a reason behind it, such as current total comp or a competing timeline, instead of deflecting the question twice
Take-Home Technical Challenge
reportedThe clock is part of the test. Three to six hours is not enough to do everything the dataset supports, so the submission mostly reveals how you spend a fixed budget against an open question. A reviewer sees which paths you took and, by absence, which you abandoned. Work that runs out of time inside the analysis ships a thin conclusion, while work that cuts scope early protects the last hour for writing. The most reliable way to lose here is to leave the scoping decision implicit, so it reads as something you missed rather than something you chose.
What to demonstrate
- Whether the scope you settled on is presented as a decision with a reason, rather than left for the reader to infer from what is missing
- Whether the depth of the work is consistent with the stated time budget, instead of several half-finished directions left open
- Whether the closing section reads as something written on purpose rather than assembled from whichever cells survived
How to prepare
- Run a timed rehearsal on a public dataset with a hard stop, holding the final sixty minutes for writing no matter where the analysis has got to
- Before opening the data, list the questions it could plausibly answer, pick one, and keep the discarded ones as a short note on what you did not attempt and why
- Commit a one-line finding after each analysis step so the writeup is assembled from recorded results rather than from memory at midnight
Presentation to Technical Panel
reportedWhere 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
Final Conversations
reportedWhere a loop ends with a senior leader, that conversation is rarely another skills test. The technical signal already exists by then, so the questions tend to open up: what you would look at first, where a metric you have heard about could mislead, what you would push back on. The decision being made is scope, which in practice means level and how much you would be trusted to own unsupervised. Treating it as a formality is the usual mistake. An open question late in the day is still being scored, and a vague answer reads as someone who has not run anything themselves.
What to demonstrate
- Whether your view of the business has anything specific behind it, given that you are working only from what is public and are expected to say so
- Whether the scope of work you describe owning matches the scope of the role, instead of sitting a level below it
- Whether you can disagree with something concrete and stay useful about it, rather than agreeing with everything said in the room
- Whether your questions are ones only this person could answer, as opposed to ones the recruiter already covered
How to prepare
- Build one view you could defend for two minutes using only public information: what the funnel probably looks like, which metric likely drives decisions, and where that metric could mislead. Being wrong for a stated reason survives this round; having no view does not
- Write down the largest piece of work you have owned from question to decision, who else touched it, and what you decided alone, then check that it reads at the level you are interviewing for
- Prepare one thing you would want changed if you joined and phrase it as a question rather than a verdict, so it opens a conversation instead of closing one
PracHub editorial advice for the preparation topics above.
Treating accounts as independent observations
Revenue is concentrated: a small number of client_ids typically carries a large share of fees, and engagements within one account share a partner, a rate card and a delivery team. Ordinary standard errors computed over engagements therefore understate uncertainty badly. Cluster at client_id, and with fewer than roughly 40 clusters use a wild cluster bootstrap or a CR2 correction, because cluster-robust standard errors are downward-biased in that regime and will manufacture significance that a replication will not reproduce.
Trending utilisation or revenue on work_date without accounting for timesheet backfill
Time entries are created days to weeks after the work happens, and the backfill tail often runs two to six weeks. A dashboard keyed on work_date therefore shows the most recent weeks as a decline that reverses on every refresh. The fix is either to hold the reporting window back past the observed backfill tail (measure the tail with the timesheet submission lag metric rather than guessing) or to report an as-of-entered_at snapshot so the series is internally consistent, and to state which one you used.
Reading a dozen metrics with no multiplicity control
Nominate one primary metric before launch and treat the rest as guardrails or exploratory, with Bonferroni or Benjamini-Hochberg applied when you intend to make claims from them. Twenty independent tests at 0.05 under the null produce at least one false positive about 64 percent of the time.
Explaining an aggregate move without decomposing the mix shift
Split the change in the aggregate into within-segment movement and movement in segment weights before you explain it. Every segment's rate can fall while the overall rate rises, purely because volume shifted toward segments that already had higher rates.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
How would you deploy the model you built in your take-home assignment …
How would you deploy the model you built in your take-home assignment to support real-time inference for a client?
Approach
- 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.
- Check what information would not exist at prediction time, and exclude it.
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?
How would you design an end-to-end data processing pipeline for unstru…
How would you design an end-to-end data processing pipeline for unstructured audio or image data?
Approach
- Check what information would not exist at prediction time, and exclude it.
- 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
- What would you monitor after launch to know the model is still valid?
- Where could label leakage enter this setup?
Why did you choose this specific model architecture over simpler basel…
Why did you choose this specific model architecture over simpler baseline models for the take-home challenge?
Approach
- Frame the prediction: the label, the moment of prediction, and the action it triggers.
- Check what information would not exist at prediction time, and exclude it.
- Set a baseline first, so any model has something honest to beat.
Follow-up
- Where could label leakage enter this setup?
- How would you choose the decision threshold, and who owns that choice?
Cluster bootstrap a margin change across few, unequal accounts
engagements has engagement_id, client_id, pricing_model, fees_usd, cost_usd and period in {pre, post} around a rate-card change. There are about 35 client_ids across roughly 400 engagements, and the top five clients carry a large share of fees. Estimate the pre-to-post change in value-weighted gross margin within pricing_model, and give a 95% interval by resampling whole client_ids with replacement, B = 2000. Write the bootstrap yourself; no resampling library. Report the account-weighted estimate alongside and explain any divergence.
Approach
- Write the statistic as a pure function of a dataframe first. Within each pricing_model, margin = (sum(fees) - sum(cost)) / sum(fees) per period; the headline is the fee-weighted average of the within-model differences using fixed pre-period weights, so a shift in the mix of work sold cannot masquerade as a change in delivery.
- Resample clusters, not rows: draw 35 client_ids with replacement and concatenate all engagements of each drawn client. A client drawn twice contributes its rows twice under distinct pseudo-ids, which is what preserves within-account correlation instead of averaging it away.
- Recompute the statistic on each replicate and take a percentile interval from the 2.5th and 97.5th quantiles. With concentrated fees the replicate distribution is skewed, so a symmetric point plus or minus 1.96 times a standard error is wrong in the tail that matters.
- Compute the account-weighted version (mean across clients of each client's margin change) next to the value-weighted one, and attribute the divergence to concentration rather than to noise; if they disagree in sign, that fact is the finding.
- Name the regime honestly: at roughly 35 clusters both cluster-robust standard errors and the pairs cluster bootstrap under-cover, so state the fix you would run next, which is a wild cluster bootstrap with Rademacher weights or CR2 with t(G-1) critical values.
Worked solution 40 min
- stat(df): pivot fees and cost by (pricing_model, period), compute per-model margins, difference post minus pre, and weight the differences by each model's pre-period fee share; return one scalar.
- Pre-split the frame into a dict of client_id to its rows, so each replicate is a concat of 35 preselected blocks rather than a boolean filter over 400 rows.
- rng = np.random.default_rng(11); for b in range(2000): draw client ids with replacement, concat their blocks, call stat, store; assert the replicate's nunique cluster draw count is 35 every time.
- ci = np.quantile(reps, [0.025, 0.975]); compute the account-weighted estimate as the unweighted mean of per-client margin changes over clients present in both periods.
- Re-run a row-level bootstrap on the same data for contrast and record both interval widths, then leave-one-client-out to see whether one account drives the point estimate.
Follow-up
- Implement the wild cluster bootstrap and show at what cluster count its p-value separates from the naive one.
- One client is 30% of fees. Show the interval with and without that account and say which one you would present, and to whom.
- What pre-period check would make you willing to call this a causal effect of the rate card rather than a correlation?
Longest consecutive bench spell per consultant from weekly activity
You have a dense series (consultant_id, week_start, billable_hours, full_week_leave BOOLEAN) covering 26 weeks for every billable-role consultant, with zero-hour weeks present as 0. Return every maximal run of consecutive weeks where billable_hours = 0, as consultant_id, spell_start_week, spell_end_week and week_count, then each consultant's longest run. Weeks flagged full_week_leave must neither break a run nor count toward its length. Mark runs still open at the last week of the series rather than reporting them as finished.
Approach
- Remove full_week_leave weeks from the sequence first, then re-number what remains with ROW_NUMBER() OVER (PARTITION BY consultant_id ORDER BY week_start). Deleting before numbering is precisely what makes a leave week transparent to a run instead of a break in it; filtering afterwards would leave a gap in the sequence and split the spell.
- Flag each surviving week as bench (billable_hours = 0) or active, then compute two row numbers per consultant: one over all surviving weeks, one partitioned additionally by the bench flag. Their difference is constant inside any run of adjacent same-flag weeks, which is the island key.
- Group the bench weeks by (consultant_id, island key) and take MIN(week_start), MAX(week_start) and COUNT(*). Do not island on week_start minus an interval times a row number: that variant assumes the spine has no holes, and the leave removal has just deliberately put holes in it.
- Pick the longest spell per consultant with ROW_NUMBER() OVER (PARTITION BY consultant_id ORDER BY week_count DESC, spell_start_week) so ties resolve deterministically, rather than a MAX that cannot carry the accompanying dates.
- Handle both edges as censoring. A spell touching the last week of the series is still open and its length is a lower bound, so flag it; a spell touching the first week may have started earlier, so either flag it too or exclude it from any length distribution and say which.
Worked solution 40 min
- Filter out full_week_leave rows and count the surviving weeks per consultant.
- Add the bench flag and the two row numbers, then print one consultant's full sequence to see the island key stay constant across a run.
- Group bench weeks by consultant and island key and aggregate to spell boundaries and lengths.
- Add the open-at-the-edge flag by comparing spell_end_week to the consultant's last surviving week.
- Rank spells per consultant and select the longest with the deterministic tiebreak.
Follow-up
- A spell begins before your 26-week window. How do you detect that, and what do you report as its length?
- Rebuild this at daily grain. What changes about weekends, holidays and the island key?
- How would you separate true bench from unbilled delivery work on a live engagement, given only charge codes?
Rebuild proposal stage timeline from a mutable event stream
fct_proposal holds proposal_id, client_id, expected_value_usd, stage, is_competitive, created_at and decided_at, and its stage column is overwritten in place. fct_proposal_stage_event holds stage_event_id, proposal_id, from_stage, to_stage and occurred_at. For proposals created in the trailing year, rebuild the ordered stage timeline with days spent in each stage, flag durations still open at the snapshot as censored, and build a funnel of distinct proposals and expected value ever reaching qualifying, scoping, submitted and decided. Reconcile each timeline's terminal stage against fct_proposal.stage and report disagreements.
Approach
- Order events per proposal and derive each stage's exit with LEAD(occurred_at) OVER (PARTITION BY proposal_id ORDER BY occurred_at, stage_event_id). The tiebreak on the event key matters because two transitions can share a second. LEAD returns NULL on the last event of every proposal, decided or not, so a NULL lead marks the end of the stream and nothing more — it is not by itself a censoring indicator.
- Close that final interval by state rather than by the NULL. Where decided_at IS NOT NULL, close it at decided_at with is_censored = FALSE; where decided_at IS NULL, close it at snapshot_date with is_censored = TRUE. Only the second group is censored, and marking it lets a consumer choose Kaplan-Meier or a restricted mean. A median computed over completed transitions alone is biased downward, because at any snapshot the still-open records are disproportionately the slow ones, and the bias grows exactly when the pipeline is slowing. A decided_at earlier than the last event's occurred_at yields a negative final interval: report those proposals, do not clamp them.
- Handle re-entry. A proposal can go scoping to submitted and back to scoping, so per-stage entries outnumber proposals. Build the funnel on COUNT(DISTINCT proposal_id) for 'ever reached stage X', and keep the per-entry rows separate for duration work.
- Build the funnel from the reconstructed timeline, never from fct_proposal.stage. That column records only where a proposal came to rest, so every proposal that passed through scoping and moved on is invisible in it and the funnel's middle collapses.
- Reconcile the last to_stage per proposal against fct_proposal.stage. Disagreements mean events are missing or a row was edited outside the event path, and the size of that set is the ceiling on how far anything else here can be trusted.
- If asked for win rate by value, exclude stage IN ('withdrawn','no_decision') from both numerator and denominator and filter is_competitive = TRUE. Those outcomes are not losses, they are not missing at random, and sole-sourced follow-on work inflates the rate.
Follow-up
- Estimate median proposal cycle time with the censored proposals included. What does the estimator need from your output?
- Two events for one proposal share occurred_at to the second. How does your ordering resolve it, and how would you detect that it happened?
- Which fields on a won proposal are populated only after the decision, and why does that rule them out of a win-rate model?
If you had an additional week to work on this data challenge, what spe…
If you had an additional week to work on this data challenge, what specific areas would you prioritize to improve performance?
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.
- Fix the population and the time window before naming any metric.
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?
Why are you interested in technology consulting, and how do you manage…
Why are you interested in technology consulting, and how do you manage the shifting priorities of contract-based work?
Approach
- Name one primary metric, then the guardrail that stops it being gamed.
- Fix the population and the time window before naming any metric.
- Restate the decision this analysis has to support, and who acts on the answer.
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?
Build a mid-delivery warning signal that survives late timesheets
Engagements are currently only found to be unprofitable at close. Build a weekly warning signal available mid-delivery from fct_time_entry (engagement_id, work_date, entered_at, hours, cost_rate_usd, status) and fct_engagement (contracted_hours, pricing_model, not_to_exceed_usd, start_date, planned_end_date, status). Timesheets arrive one to three weeks after work_date, so recent weeks always look light. Define the metric, the correction you apply for late arrival, and the alert threshold by pricing model. State the false-alert rate your threshold implies and how you would measure it.
Approach
- Define the signal as schedule-normalised burn: corrected hours to date divided by contracted_hours, divided again by elapsed days over planned duration. Above 1 means hours are consuming the budget faster than the calendar consumes the schedule. It is defined only where contracted_hours IS NOT NULL, so retainer and outcome_based work needs separate treatment rather than a silent NULL-drop.
- Measure the backfill curve before correcting anything. For historical work_dates D, compute f(k), the share of the eventual hours for D that existed as of D plus k days, for k from 1 to 45. Do not assume a shape. Divide observed hours in the most recent weeks by the fitted f(k).
- State what the correction assumes and where it breaks. f(k) is estimated pooled but varies by level and practice area, and it shifts around month-end billing cycles and holidays. Fit it by practice_area at minimum, and widen the alert band on recent weeks, since dividing by a small f(k) multiplies the estimation error along with the hours.
- Set the threshold per pricing model, because the loss function differs. On fixed_fee, every hour past budget is margin, so hours against contracted_hours is the right comparison. On capped time_and_materials, hours are harmless until cumulative billable value approaches not_to_exceed_usd, so the signal should track value against the cap. On uncapped time_and_materials, overrun is revenue, and a burn alert there is noise.
- Quantify the operating characteristic instead of shipping a bare threshold. Replay the rule on closed engagements at every delivery week, label each by whether its final gross margin fell below target, and report precision and recall at two or three thresholds so the reader can pick the operating point.
Worked solution 45 min
- Build the backfill curve: for work_dates between 120 and 45 days old, compute cumulative hours as of work_date plus k against the final total, and take the median share at each k.
- Compute schedule-normalised burn weekly for all active engagements with contracted_hours NOT NULL, correcting the trailing six weeks by the fitted f(k).
- Backtest by replaying the rule on closed engagements at every delivery week, labelling each engagement by whether its final gross margin fell below target.
- Produce a precision and recall table at three thresholds and choose one, stating the operating point in the rule itself.
- Write the separate rule for capped time_and_materials against not_to_exceed_usd, and declare retainer and outcome_based out of scope for this signal.
Follow-up
- Your corrected series still dips in the latest week. Is the correction wrong or is the dip real, and what distinguishes the two?
- An engagement has contracted_hours NULL under a retainer pricing model. What is your signal there, and what do you tell the delivery lead in the meantime?
Utilisation fell three straight weeks; test entry lag first
A weekly billable-utilisation dashboard keyed on fct_time_entry.work_date shows three consecutive declines, roughly 74% to 66%. Billable headcount, the leave calendar and the holiday calendar are unchanged. You have fct_time_entry (consultant_id, work_date, entered_at, approved_at, hours, is_billable, charge_code, status) and dim_consultant (consultant_id, fte_fraction, is_billable_role, hire_date, termination_date). Decide whether the decline is real or an artefact of when timesheets are entered. Deliver a corrected series, the reporting cutoff you would adopt, and the evidence for that cutoff.
Approach
- Freeze the denominator before touching the numerator: available hours = scheduled workdays x 8 x fte_fraction, minus approved leave hours and region public holidays, prorated for hire_date and termination_date inside the week. Confirm it is flat across the three weeks, so any movement must be numerator.
- Measure the backfill curve instead of guessing it: for each work_date D in a mature window, compute the share of that day's eventual hours whose entered_at <= D + k, for k = 1..45. Read off k*, the lag at which the curve reaches about 99%.
- Restate at equal maturity: recompute every week counting only entries with (entered_at::date - work_date) <= k*, and suppress any week younger than k* days. Weeks are then comparable because each is observed at the same age.
- Check the approval gate separately: the metric counts status = 'approved', so a backlog of 'submitted' or 'rejected' rows reproduces the same shape with no entry lag at all. Plot hours by status per week.
- If the equal-maturity series is flat, report an artefact and publish the cutoff; if it still falls, segment by charge_code (client_delivery versus bench, training, internal_project) and by practice_area to locate the real movement.
Follow-up
- The backfill tail itself lengthened this quarter. Is that a finding, and what would you do with it?
- Leadership needs a number for the current week today. What do you give them, and how do you label it?
- How would this analysis change if approval, not entry, were the slow step?
For someone who has spent the last year in notebooks, dashboards or modelling work and has not written raw SQL under time pressure. The first four days rebuild query fluency against a fixture you control and can verify by hand; the last three attach that fluency to the rest of the loop.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Build a fixture you can check answers against
- Create a local Postgres or SQLite database with four tables (users, sessions, events, orders) holding roughly 200 rows you generated yourself, so you know the contents well enough to predict every result.
- Deliberately seed the cases that break queries: a user with no sessions, a session with no events, two orders sharing a timestamp, a NULL in one join key, and one duplicated user row.
- Before writing any SQL, hand-compute five answers on paper (how many users placed at least one order, median orders per ordering user, and three others) and save them as the ground truth for the week.
Deliverable: A one-command seed script plus a text file of five hand-computed answers to grade every later query against.
Practice prompt ↗Practice prompt ↗Worked solution ↗02Joins, filters and NULL semantics
- Answer "which users have no orders" three ways (LEFT JOIN with IS NULL, NOT EXISTS, NOT IN) and confirm that the NOT IN version returns zero rows once the subquery contains a NULL, because the comparison is never TRUE.
- Reproduce the LEFT JOIN that silently collapses to an inner join by putting a right-table predicate in WHERE, then fix it by moving the predicate into the ON clause, and record both row counts.
- Create a fan-out bug on purpose by joining orders to order_items and summing the order total, then correct it with a pre-aggregated subquery and explain in one line which table changed the grain.
Deliverable: One annotated .sql file holding the three join traps, each with the wrong result and the corrected result side by side.
Practice prompt ↗Practice prompt ↗03Window functions and frames
- Write three window queries against the fixture: a running order total per user, the rank of each order within its user by value, and the day gap to that user's previous order, then check each against the day-one ground truth.
- Run ROW_NUMBER, RANK and DENSE_RANK over a column containing ties, print all three side by side, and write one sentence on when each is the correct choice.
- Switch one query from the default frame (RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, which is what you get when ORDER BY is present and no frame is written) to ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, and explain why the output differs only when the ORDER BY column has duplicates.
Deliverable: Three verified window queries plus a short note explaining the RANGE versus ROWS difference in your own words.
Practice prompt ↗Practice prompt ↗04The four analytical query patterns
- Write a monthly retention grid: first order month per user, then months-since-first as the column, and verify that month zero equals the cohort size exactly.
- Sessionize the events table under a 30-minute inactivity rule using LAG plus a cumulative sum over a new-session flag.
- Build a four-step funnel that counts distinct users rather than events at each step, and state the rule you applied to a user who reaches step three without ever logging step two.
Deliverable: One file with the retention, sessionization and funnel patterns, each carrying a one-line note on the assumption it bakes in.
Practice prompt ↗Practice prompt ↗Worked solution ↗05Write SQL the way you will have to write it live
- Set a 12-minute timer and solve three medium prompts in a plain editor with no execution and no autocomplete, then run them and tally syntax errors separately from logic errors.
- Narrate one solution aloud while writing it, stating the grain of each intermediate result (one row per user, one row per user-day) before you type its body.
- Rewrite your slowest solution as a CTE chain where every CTE name states its grain, and time yourself re-solving it from blank.
Deliverable: A recording of one narrated solution plus an error tally that separates syntax from logic.
Practice prompt ↗Practice prompt ↗06One day for everything that is not SQL
- Write the preconditions of the two-sample t-test from memory, then check them: independent observations, and a difference in means whose sampling distribution is approximately normal, which at large sample sizes follows from the central limit theorem rather than from normality of the raw values.
- Write the difference between an odds ratio from logistic regression and a relative risk, and state the condition under which the two are close (low outcome prevalence).
- Prepare a 90-second answer to "how would you know this model is any good" that names the metric, the baseline you would beat, and the cost of the errors you care about.
Deliverable: One page of notes covering test preconditions, the odds-ratio caveat and the model-quality answer.
Practice prompt ↗Practice prompt ↗07Full loop rehearsal
- Run a 45-minute mock with someone willing to interrupt: 20 minutes of SQL, 15 minutes defining a metric, 10 minutes on a past project.
- Re-solve from blank the two queries you were slowest on this week and compare the times against day five.
- Write a five-line answer to "walk me through a project" that puts a number in the first sentence and names the decision the work changed.
Deliverable: Mock feedback notes plus a timed project narrative you can deliver without reading it.
Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
Saying no well is a senior skill and it is rarely rehearsed. Think of a time you told someone their analysis was not worth doing, or that the experiment could not answer their question at the sample size available. Explain what you offered instead. Refusal without an alternative reads as obstruction rather than judgement.
How do you handle a situation where a client's data is of much lower q…
How do you handle a situation where a client's data is of much lower quality than initially promised?
Approach
- Pick a story where you drove the decision, not one where you observed it.
- Quantify the outcome, including what you would not claim credit for.
- Close with what you would do differently, concretely.
Follow-up
- What would you do differently if you ran that project again?
- What did you decide not to do, and why?
Explain how your model's performance directly translates to business v…
Explain how your model's performance directly translates to business value or cost savings for the stakeholder.
Approach
- State the situation in two sentences and spend the rest on your reasoning.
- Close with what you would do differently, concretely.
- Pick a story where you drove the decision, not one where you observed it.
Follow-up
- What did you decide not to do, and why?
- What would you do differently if you ran that project again?
Answering a request to redefine the utilisation denominator
Before a quarterly review, a practice lead asks for two changes to reported billable utilisation: drop consultants allocated to engagements with status = 'paused' out of the denominator, and count approved travel hours booked to client_delivery as billable. Together the changes move reported utilisation from 71 to 79 percent. You must answer in writing today. Give your answer, the principle you apply to each change separately, and what you do if you are overruled.
Approach
- Name the probe: whether you can hold a measurement line by adjudicating each proposal on its merits, rather than treating the request as bad faith or accepting it to avoid a fight.
- Adjudicate the paused-engagement change against what the denominator means. Billable utilisation measures the share of available consultant hours that were sold. A consultant whose engagement is paused is still available and still costs salary, which is precisely the situation the metric exists to reveal. Removing them defines away the signal, so this one is refused on definitional grounds, not on suspicion.
- Adjudicate the travel change as an empirical question, because it is one. If those hours are billed to the client, is_billable and bill_rate_usd should already reflect it; if they are charged and then written off or never invoiced, counting them raises utilisation while realisation falls. Check the invoice lines for the same engagement and period rather than arguing about principle.
- Quantify each change separately, and the interaction. An eight-point move made of one point and seven points is a different conversation from two four-point moves, and the lead cannot make a good decision without that split.
- If a change is accepted, restate history on the new definition and publish the old series alongside it with the date of the change. A definition applied forward only makes every period-over-period comparison meaningless. If overruled, comply with the restatement and the footnote, and escalate only the part that is a misstatement of fact rather than a defensible choice.
Follow-up
- Which of the two changes would you accept, and what evidence flips your answer on the other one?
- The lead wants the new definition applied only from this quarter forward. Why is that worse than applying it to history, and how do you explain that in one sentence?
- 01
How do you handle a situation where a client's data is of much lower quality than initially promised?
- 02
Explain how your model's performance directly translates to business value or cost savings for the stakeholder.
- 03
Before a quarterly review, a practice lead asks for two changes to reported billable utilisation: drop consultants allocated to engagements with status = 'paused' out of the denominator, and count approved travel hours booked to client_delivery as billable. Together the changes move reported utilisation from 71 to 79 percent. You must answer in writing today. Give your answer, the principle you apply to each change separately, and what you do if you are overruled.
Is this an official SFL Scientific interview guide?
No. It is PracHub's own research and practice material for the Data Scientist role at SFL Scientific. Rounds and questions reflect what candidates have reported, not a process SFL Scientific has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗How difficult is the SFL Scientific interview process?
The process is moderately difficult but highly time-consuming. The take-home data challenge can require upwards of 15 to 20 hours of focused effort to complete to the standard expected by the grading team.
PracHub interview research ↗What format should I use to submit my take-home assignment?
You should submit your work as a highly polished, well-commented Jupyter Notebook. The grading team expects to see step-by-step markdown explanations, clean visualizations of your data and results, and easily executable code blocks.
PracHub interview research ↗How technical are the interviews with the founders and executive leadership?
The executive rounds tend to focus more on high-level architecture, business translation, and communication style. While they may ask fundamental technical questions, their primary goal is to assess how well you would represent the firm in front of senior client stakeholders.
PracHub interview research ↗Are the job offers at SFL Scientific stable?
Because hiring is closely aligned with client consulting contracts, the firm's hiring needs can change quickly. Candidates should maintain active communication with HR throughout the process to ensure alignment on contract timelines and role availability.
PracHub interview research ↗Sources & methodology 3 sources ↗
No official company page is cited. Rounds and questions come from candidate reports and PracHub editorial material; each source shows the date it was read.
- 01PracHub interview research ↗
PracHub editorial research into this company and role, maintained with this guide. Candidate-reported, not an employer publication.
PracHub page · Accessed 2026-09-22 - 02PracHub Data Scientist practice ↗
Cross-company practice questions for this role.
PracHub page · Accessed 2026-09-22 - 03PracHub interview preparation framework ↗
The framework the preparation plan follows.
PracHub page · Accessed 2026-09-22