A Data Scientist at The City Of Providence serves as a vital bridge between complex municipal datasets and actionable public policy. You are responsible for transforming raw data into insights that improve city services, optimize resource allocation, and enhance the quality of life for residents. Your work directly impacts how the city functions, from urban planning and public safety to administrative efficiency and community engagement.
This role requires a unique blend of technical proficiency and civic-minded problem-solving. You will work within a fast-paced government environment where your analytical findings can influence real-world outcomes for thousands of citizens. It is an opportunity to apply advanced statistical modeling and machine learning techniques to high-impact public sector challenges that require both rigorous methodology and a clear understanding of the municipal landscape.
Given the nature of public sector work, focus your preparation on how data-driven decisions can solve real-world community problems rather than purely commercial optimization.
Initial Screening
reportedData Scientist covers at least four different jobs: experimentation, product analytics, causal work on observational data, and applied modelling that ships into a system. A screening call is the cheapest place to find out which of them is being hired for, and doing that diagnosis openly reads as senior rather than fussy. Ask what the last few pieces of work on the team actually were, and roughly how a week splits between querying, modelling and stakeholder time. Then say which parts of that you have done and which you have not. Claiming the whole range is the fastest way to be caught one round later.
What to demonstrate
- Whether you can distinguish the flavours of the role and locate your own experience inside one of them honestly
- Whether you name what you have not done instead of stretching to cover every line of the posting
- Whether your hard constraints (notice period, location, work authorisation, level) surface now rather than at offer stage
How to prepare
- Map the last two years of your time into rough percentages across query writing, experiment design, modelling and stakeholder work, so a question about scope has a real answer
- Mark every responsibility in the posting as done, adjacent or new, and prepare one sentence for each adjacent item naming the closest thing you have actually built
- Decide which logistics are non-negotiable before the call so you can state them in one sentence rather than negotiating live
Technical Review
reportedThis round decides whether someone can hand you a schema and a question and trust the number that comes back. Correctness under a clock is the bar, not clever syntax. The habit that separates strong from weak answers is checking the grain: after every join, know how many rows you expect and whether the count moved. Most wrong answers in this format are not wrong logic, they are a fan-out from a key that turned out not to be unique, or a filter applied before an aggregate when it belonged after. Say what you expect before you run it.
What to demonstrate
- Whether your row counts survive each join, and whether you notice on your own when they do not
- Deliberate handling of rows that fail to match, including whether the question needs an inner join or a left join with the non-matches kept and counted
- Whether NULLs are treated on purpose, given that a NULL compares equal to nothing and that COUNT of a column skips it
- Reaching a defensible answer inside the window instead of a refined one after it
How to prepare
- Take a two-table schema, write a join that fans out on purpose, then fix it by collapsing the many-side to one row per key before joining. Repeat until the fix is reflex rather than recall.
- Write a funnel as one query and print the distinct user count at each stage, then confirm each stage is a subset of the one above it rather than assuming it
- Do a few timed runs in a plain text box with no autocomplete and no formatter, since assessment editors often have neither
Behavioral Discussions
reportedThis round decides whether you owned a decision or watched one happen nearby. Interviewers for data roles listen for the point where the analysis stopped being a report and started changing what someone did, so build each story around that hinge: what was going to happen by default, what you found, and what happened instead. The most common weakness is a story that ends at delivery. If you can name the decision your work changed and the number that moved because of it, most follow-ups become easy.
What to demonstrate
- Whether the decision was yours to influence, or whether you are narrating a team outcome in the first person
- The counterfactual: what would have been done without your analysis, and why that default was worse
- How far your involvement ran past the handoff, and whether you checked that the change did what you predicted
How to prepare
- Pick three projects and write one sentence for each naming the decision-maker, the choice in front of them, and what they chose after seeing your work. If you cannot name a person and a choice, the story is not ready for this round.
- Reconstruct the baseline for your strongest project from the original query or dashboard rather than memory, so the before-number survives a follow-up asking where it came from.
- Prepare an honest version of a project where your recommendation was overruled, including what you did with the analysis afterwards.
Final Assessment
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
PracHub editorial advice for the preparation topics above.
Computing coverage or take-up with an administrative denominator, for example approved applications divided by all applications
That ratio measures throughput among people who already found the system, not delivery to the people entitled to the service. It gets better when outreach is cut, because the marginal applicant is the one most likely to be denied or to abandon, and it gets worse when a new access channel brings in harder cases. The correct denominator is a modelled eligible population from survey microdata run through the eligibility rules, and the gap between it and the applicant count is usually the finding.
Reporting mean or median days to decision over cases that have already been decided
Cases still open at the extract are exactly the slow ones, so restricting to decided cases is length-biased. The pathology is that the metric improves as the backlog of hard cases grows, because those cases are excluded from the sample precisely while they are getting worse. Treat undecided cases as right-censored at (extract_date - submitted_at) and use a Kaplan-Meier estimate, or report the cohort completion curve at fixed horizons such as share decided by day 30 and day 90.
Sizing estimates built on unnamed, unrevisable assumptions
Write each assumption as a named number you can change, then show the arithmetic so the interviewer can challenge one input instead of the whole answer. Finish by saying which assumption the result is most sensitive to, which matters more than the point estimate.
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.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Kaplan-Meier days to decision with Greenwood standard errors
apps has application_id, submitted_at, decision_at (NaT while open) and status, and you have a scalar extract_date. Estimate time from submission to decision in days using a Kaplan-Meier survivor function written from scratch. Rows still in status 'submitted' or 'pending_evidence' are right-censored at extract_date minus submitted_at. Return S(t) with Greenwood standard errors, the median, and S at 30, 60 and 90 days. Report the mean over decided rows alongside it. No lifelines and no statsmodels.
Approach
- Build duration and an event indicator in one pass: decided rows get event = 1 with duration = (decision_at - submitted_at).days, open rows get event = 0 with duration = (extract_date - submitted_at).days. Count negative durations from backdated decisions and report them rather than clipping to zero, which would invent same-day decisions.
- Assemble the risk table: distinct durations ascending, d_i events at each, c_i censored at each, and n_i at risk. Use the standard tie convention that observations censored at time t are still at risk at t, so events are credited before censoring at the same duration.
- S(t) is the cumulative product of (1 - d_i / n_i) over event times up to t. Greenwood gives Var(S(t)) = S(t)^2 * sum(d_i / (n_i * (n_i - d_i))) over the same times. Each term of that sum grows as n_i thins, but the sum is multiplied by a falling S(t)^2, so the standard error is not monotone in t: it climbs early, peaks in the interior of the curve, and falls again once S(t) is small. Guard the term where n_i == d_i, which divides by zero at a last event that empties the risk set.
- Median is the smallest t with S(t) <= 0.5. If the curve never crosses 0.5 inside observed follow-up, return 'not reached'. Substituting the largest duration there is the same mistake as dropping censored rows, in a different costume.
- Put the decided-only mean next to it and state the mechanism, not just the gap: open cases are disproportionately the slow ones, so restricting to decided rows is length biased and the bias grows as the backlog grows.
Follow-up
- Two programs report the same Kaplan-Meier median but S(30) differs by 15 points. Which do you escalate, and what do you ask for first?
- A case can also exit by withdrawal or administrative closure. What does a single curve on 'any exit' get wrong, and what estimator replaces it?
- Decision events are sometimes backdated by up to three weeks. Which direction does that move the curve, and how would you bound the effect?
Permutation test for a stratified first-response equity ratio
requests has service_request_id, request_type_code, priority, deprivation_quartile, reported_at and first_response_at (NaT while open), plus a scalar extract_date. Compute the first-response equity ratio: within each (request_type_code, priority) stratum, the 90th percentile of response hours in quartile 4 divided by the same percentile in quartile 1, then combined across strata on the log scale. Test it against a null of no quartile effect using 10,000 permutations that shuffle quartile labels inside each stratum. Report a one-sided p-value. Do not use a library permutation routine.
Approach
- Compute response_hours, and treat still-open requests as right-censored at extract_date. Take the 90th percentile from a Kaplan-Meier estimate of the response distribution and drop any stratum whose survivor never reaches 0.10, reporting it as unidentified rather than quietly taking the percentile of responded rows.
- Statistic per stratum is log(p90 in quartile 4) - log(p90 in quartile 1). Combine across strata as a weighted mean of logs with weight min(n_q1, n_q4), then exponentiate. A ratio of pooled percentiles answers a different question and mostly reports the request-type mix.
- Permute inside strata: shuffle quartile labels among the quartile 1 and quartile 4 rows within each (request_type_code, priority) cell. Pooling the shuffle tests a null nobody asked about, namely that the mix of request types is identical across quartiles.
- Precompute per-stratum integer index arrays once and recompute only the statistic inside the loop. Ten thousand draws across every stratum is otherwise slow enough that people quietly cut B to 500 and stop reporting it.
- One-sided p-value is (1 + count of permuted statistics >= observed) / (B + 1). Report the null median too, which should sit near a log ratio of 0, as a check that the shuffling is doing what you think.
Follow-up
- The ratio is 1.6 with p = 0.002. What else do you need before writing that response times are inequitable?
- Quartile 4 requests skew toward phone reporting and toward emergency priority. Which of those belongs in the strata and which does not, and why?
- One stratum carries 70 percent of the combined weight. How do you report the result, and what sensitivity do you run?
Resolve service-request reopen chains and score the 30-day rate
requests has service_request_id, reported_at, closed_at, closure_code, request_type_code and reopen_of_request_id, a nullable self reference to the earlier request this one repeats. Resolve the chains into episodes: give every row a root id, a depth and an ordinal, without recursion and without looping forever on a cycle. Then compute the 30-day reopen rate, where the denominator is requests closed in the window with closure_code 'resolved' and the numerator is those with a child reported within 30 days of closed_at. Report dangling parent references and any cycle you find.
Approach
- Separate genuine roots (reopen_of_request_id is null) from dangling references where the parent id is absent from the table. Rooting a dangling child silently welds two episodes together and hides an extract that was cut mid-chain.
- Walk with iterative pointer jumping: each pass remaps every row to its parent's parent, so the remaining distance to the root halves per pass and about 20 passes cover a chain a million deep. Point every root at itself so the jump is a no-op once a row has arrived, and stop when no pointer changes rather than at a fixed count.
- Carry a distance vector alongside the pointer or the depth column is wrong. Under doubling a row at true depth d stops moving after about ceil(log2 d) passes, so the pass number is log2(depth) and not depth: a chain 8 deep stabilises on pass 3. Hold dist with the invariant that dist[i] is the number of edges from i to ptr[i], initialised to 1 for every child and 0 for every root, and update dist[i] += dist[ptr[i]] and ptr[i] = ptr[ptr[i]] together from the previous pass's arrays. At the fixed point ptr[i] is the root and dist[i] is the true depth.
- Detect cycles by capping iterations at ceil(log2(n)) + 1 and flagging rows whose pointer is still moving. A cycle means a request was recorded as a reopen of its own descendant, which is a data error and must be quarantined rather than reported as an episode.
- Assign ordinal within root by reported_at, then compare that ordering against the foreign-key chain order. Disagreement means backdated reported_at values and belongs in the data-quality report, not silently inside the metric.
- For the metric, join each qualifying closed request to its children and keep those with 0 <= reported_at - closed_at <= 30 days. A child reported before its parent closed is an error, not a reopen, and counting it inflates the numerator.
- Apply the exclusion of duplicate, invalid and closed_on_age to both numerator and denominator, then report the rate both ways so the size of the flattery is on the page rather than in a footnote.
Worked solution 40 min
- Build a parent Series indexed by service_request_id, mark ids whose parent is not in the index as dangling, and set those pointers to null with a reason recorded.
- Initialise ptr = parent.fillna(own id) and dist = parent.notna().astype(int), then iterate on the previous pass's values: dist_next = dist + ptr.map(dist) and ptr_next = ptr.map(ptr), assigning both together. Stop at a fixed point of ptr or at the iteration cap, and treat rows still moving at the cap as cycle candidates.
- Read depth off the final dist rather than off the pass number at which a row stabilised, which is log2 of the depth, then rank reported_at within root for ordinal and diff ordinal against depth.
- Filter the denominator to closed_at in the window with closure_code == 'resolved', left-join children on reopen_of_request_id, and count parents with at least one child inside the 0 to 30 day band.
- Recompute the same numerator against a denominator that keeps administrative closures, and return both rates with the episode frame, the dangling list and the cycle list.
Follow-up
- Two crews close the same kind of work differently, one as 'resolved' and one as 'referred_external'. What does that do to this metric, and how would you detect it from this table alone?
- A chain crosses a request_type_code retirement, so parent and child carry different codes. How do you attribute the episode, and what do you report by type?
- The rate is 12 percent overall and 3 percent in one district. Give two explanations that are not about work quality.
Submission completion rate by intake channel, thirty-day window
fact_application has application_id, channel (online, phone, mail, in_person, kiosk, partner_org), created_at, submitted_at (NULL if the draft was never submitted) and status. Report, per weekly created_at cohort and per channel, the number of applications started, the number submitted within 30 days of created_at, and the completion rate. Include only cohort weeks that closed at least 30 days before the extract date. Rows left in status 'draft' or 'abandoned' stay in the denominator. Return cohort_week, channel, started, submitted_30d, completion_rate.
Approach
- Bucket on created_at, not submitted_at: the cohort is defined by when the work entered the system, so a row that never submits must still land in a cohort week.
- Express the numerator as a conditional aggregate, COUNT() FILTER (WHERE submitted_at IS NOT NULL AND submitted_at < created_at + INTERVAL '30 days'), so the denominator stays COUNT() over the same grouped set and cannot drift from it.
- Cut incomplete cohorts with a WHERE on the cohort week rather than on individual rows, and anchor the maturity test on the week's last possible created_at, not its first. A week runs [week_start, week_start + 7 days), so the latest application in it needs 30 more days and the week qualifies only when week_start + INTERVAL '37 days' <= extract_date. Testing week_start + 30 days admits a week whose Sunday arrivals have had 23 days of exposure, and that week reports a depressed rate that reads as a regression.
- Group by channel from the start rather than pooling then splitting, because channel mix changes week to week and a pooled rate moves with the mix.
- Return the raw numerator and denominator next to the rate so a reader can see which channels carry enough volume for the rate to mean anything.
Worked solution 20 min
- Write the cohort key as date_trunc('week', created_at) and confirm the row count of the base table equals the sum of the started column, proving nothing was dropped by the grouping.
- Add the 30-day conditional count and verify submitted_30d never exceeds started for any group.
- Filter to cohort weeks where date_trunc('week', created_at) + INTERVAL '37 days' <= extract_date: 7 days to carry the week out to its last created_at, plus the 30-day submission window. Under date_trunc a week starts Monday, so this is the Monday whose following Sunday's arrivals have all had their full 30 days.
- Compute completion_rate as submitted_30d::numeric / NULLIF(started, 0) to avoid a divide-by-zero on a channel with no starts in a week.
- Scan the output for channels sitting at exactly 1.000 and note them as a measurement artefact rather than a result.
Follow-up
- mail and in_person come back at exactly 1.000 every week. Is that a bug, and how would you present the metric so a reader does not read it as those channels performing best?
- How would the query change if you wanted completion within 30 days measured in business days against the published service standard instead of calendar days?
- A channel's completion rate rises 8 points the week a new form launches. What would you check before reporting it as an improvement?
Rank small-area request rates with denominator error propagated
fact_service_request has service_request_id, geo_id, reported_at and request_type_code. dim_geography has geo_id, geo_code, geo_level, vintage_year, population_estimate and population_estimate_moe, the margin of error published at 90 percent confidence. For geo_level = 'block_group' and a single request_type_code over 12 months, return requests per 1,000 residents, the standard error of that rate, and a rank. Exclude areas whose rate carries a relative standard error above 30 percent, and report beside each surviving area the rank it would have received under a naive ranking of every area by point estimate alone.
Approach
- Convert the published margin to a standard error with se_pop = moe / 1.645, which holds because the margin is published at 90 percent confidence and 1.645 is the corresponding normal quantile; if the publisher used 95 percent the divisor is 1.96 instead, so the confidence level has to be read off the documentation rather than assumed.
- Treat the numerator as fixed: the request count is an administrative census of reports, not a sample, so the only sampling variance in the rate comes from the denominator. The delta method then gives se_rate = rate * (se_pop / population_estimate), meaning the relative standard error of the rate equals the relative standard error of the population estimate.
- Use that identity to make the filter cheap and honest: drop areas where se_pop / population_estimate > 0.30. The same first-order approximation degrades as the denominator RSE grows, so the cut is doing double duty as a validity condition for the formula it is derived from.
- Compute both ranks with window functions in a CTE before filtering, since a window function is evaluated after WHERE and cannot be filtered in the same query level; rank the full set first, then apply the RSE filter in an outer query so the naive rank still reflects the unfiltered ordering.
- Add a screening flag for areas whose rate ± 1.645 * se_rate overlaps the interval of the top-ranked surviving area, and describe it as a conservative indicator that the two are not distinguishable rather than as a formal test of difference.
- Exclude population_estimate = 0 and NULL before dividing, and state the exclusion count in the output so nobody reads the filtered list as the complete geography.
Follow-up
- The five highest-rate block groups all disappear under the RSE filter. What would you publish, and what would you say in the memo about the areas you dropped?
- If the numerator were a survey estimate too, how would the variance calculation change, and what would you need from the publisher?
- The request_type_code was split into two codes midway through the 12 months. What does that do to this ranking?
How do you prioritize projects when faced with competing demands from …
How do you prioritize projects when faced with competing demands from different city departments?
Approach
- State what result would change your recommendation, so the answer is falsifiable.
- Decompose the metric into the rates that drive it, and say which one you would check first.
- Restate the decision this analysis has to support, and who acts on the answer.
Follow-up
- 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?
Explain the difference between supervised and unsupervised learning in…
Explain the difference between supervised and unsupervised learning in the context of predicting city service demand.
Approach
- 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.
- Restate the decision this analysis has to support, and who acts on the answer.
Follow-up
- What would you do if the primary metric and the guardrail moved in opposite directions?
- How would you detect that the metric is being gamed rather than genuinely improving?
What statistical methods would you use to measure the impact of a new …
What statistical methods would you use to measure the impact of a new city policy?
Approach
- Fix the population and the time window before naming any metric.
- Name one primary metric, then the guardrail that stops it being gamed.
- 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?
How would you handle missing or incomplete data in a municipal dataset…
How would you handle missing or incomplete data in a municipal dataset?
Approach
- Decompose the metric into the rates that drive it, and say which one you would check first.
- Name one primary metric, then the guardrail that stops it being gamed.
- Restate the decision this analysis has to support, and who acts on the answer.
Follow-up
- What would you do if the primary metric and the guardrail moved in opposite directions?
- How would you detect that the metric is being gamed rather than genuinely improving?
Variance reduction with pre-period covariates and stratified blocks
A district-level experiment on a reminder for outstanding evidence will run six weeks across 46 service districts. The outcome is the share of evidence requests satisfied within 14 days, computed per district. District outcomes are stable: the correlation between a district's pre-period share and its in-period share is 0.62. You have 23 districts per arm and cannot get more. Specify a CUPED adjustment and a stratification scheme, and quantify what each one buys in MDE terms.
Approach
- Choose a covariate fixed before randomisation and untouchable by treatment: the same district's pre-period outcome over a window ending before assignment, which is why that window has to be frozen in the design document rather than chosen after seeing results.
- Apply the adjustment as Y_adj = Y - theta * (X - mean(X)) with theta = Cov(Y, X) / Var(X), the ordinary least squares slope of Y on X estimated on the pooled arms; the estimate stays unbiased because X is pre-randomisation and the same theta is applied to both arms.
- Quote the variance result exactly: Var(Y_adj) = Var(Y) * (1 - rho^2), so the standard error falls by sqrt(1 - rho^2), the MDE falls by the same factor, and the effective sample multiplies by 1 / (1 - rho^2).
- Stratify instead of simple-randomising: block on deprivation quartile crossed with a pre-period volume tercile, assign inside blocks, and then include the block indicators in the analysis model, because a stratified design analysed without strata terms is valid but conservative and throws the gain away.
- State the limits plainly: CUPED reduces variance and does nothing about bias, it cannot rescue a broken assignment, and at 46 units the gain competes with the degrees of freedom the adjustment costs, so report adjusted and unadjusted estimates side by side.
Worked solution 30 min
- Compute 1 - rho^2 = 1 - 0.62^2 = 0.6156, the multiplicative factor on the variance.
- Standard error ratio = sqrt(0.6156) = 0.785, so the MDE falls by 21.5 percent.
- Effective sample multiplier = 1 / 0.6156 = 1.62, so 46 districts behave like about 75.
- Estimate theta as the OLS slope of in-period on pre-period share across all 46 districts, then difference the adjusted arm means.
Follow-up
- What breaks if you use a covariate measured during the experiment window, such as in-period request volume, instead of a pre-period one?
- Would you rather have rho = 0.62 on 46 districts or rho = 0 on 70 districts, and how do you decide?
Equity ratio doubles after a new reporting channel opens
The rolling-90-day first-response equity ratio (the 90th percentile response time in deprivation quartile 4 divided by the same percentile in quartile 1) moved from 1.3 to 2.6 in one month. Tables: fact_service_request (reported_at, first_response_at, request_type_code, priority, channel, geo_id, assigned_unit_id, sla_hours, closure_code) and dim_geography (geo_id, deprivation_quartile, vintage_year). Decompose the move into measurement, mix and real service change, and attach an interval to the residual.
Approach
- Handle censoring before anything else. A percentile of (first_response_at minus reported_at) computed over responded rows only is biased downward, because requests still awaiting a first response are exactly the slow ones and carry NULL. Either estimate the time-to-first-response quantile with those rows right-censored at the extract date, or switch to the share responded inside sla_hours, restricted to requests whose SLA window closed before the extract so a NULL is a definite miss.
- Verify the metric is computed within request_type_code and priority and only then aggregated with fixed weights. The mix of request types differs by quartile, so a pooled percentile ratio measures mix. A new channel such as sensor or mobile_app entering one quartile with a near-instant automated first response will crush that quartile's percentile and double the ratio with no change in field service.
- Cut by assigned_unit_id and by shift. Units are not evenly distributed across quartiles, so a vacancy, a reassignment or a queue transfer moves one unit's right tail and therefore one quartile's percentile. Check whether the move is a single unit before calling it a systemic gap.
- Check the geography join and the quartile derivation. deprivation_quartile is derived within parent geography and vintage, so a re-derivation or a vintage change reshuffles which areas sit in quartile 4, and the two quartiles being compared may not contain the same places as last month.
- Separate mix from within-cell change by recomputing with base-period weights across request type and priority, then bootstrap the residual: resample requests with replacement within each quartile stratum, recompute the percentile ratio a thousand times, and report the percentile interval, because a ratio of order statistics in a heavy right tail has no usable closed-form standard error.
Follow-up
- The whole move is a new automated channel in quartile 1. Is the equity finding therefore void, or is there still a real disparity to report, and how do you frame it?
- How would you redesign this metric so that adding a channel cannot move it, while it still detects a genuine widening?
For someone who can already write the query and train the model but stalls when asked what to measure or whether a change is worth making. Metric definition and case structure come first; the technical work is kept as maintenance rather than the centre of the week.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Metric anatomy
- For three products you use daily, write one primary metric, two input metrics that plausibly move it, and one guardrail that would catch a cheap way of moving the primary at the cost of the product.
- For one of them, specify the metric precisely enough that two analysts would return the same number: numerator, denominator, unit of observation, time window, and how returning and deleted accounts are treated.
- Pick a ratio metric and write what happens to it when the denominator shrinks for reasons unrelated to the numerator, with a concrete example of that happening.
Deliverable: A one-page metric tree for one product, with the primary metric written as an unambiguous spec.
Practice prompt ↗Practice prompt ↗Worked solution ↗02Diagnosing a drop without guessing
- Take the prompt "weekly active users fell 8 percent week over week" and write the segmentation plan before proposing any cause: platform, region, tenure cohort, acquisition channel, and whether the movement sits in the numerator or in a changed denominator.
- List the instrumentation failures that manufacture fake drops (a client release that stopped firing an event, a bot filter change, a shifted date boundary or timezone) and write the query that rules out each one.
- Rehearse stating the boring explanations first, seasonality and day-of-week composition, before reaching for a product cause.
Deliverable: A drop-diagnosis checklist short enough to recite from memory in under a minute.
Practice prompt ↗Practice prompt ↗03Should we build it
- Take a feature idea and write it as a bet: what you believe is true, what would have to be true for it to pay off, the metric that would confirm it, and the effect size that would justify the engineering cost.
- Size the opportunity top-down and bottom-up, then reconcile the two numbers in writing instead of quoting whichever is friendlier.
- Write the counter-metric that would make you kill the feature even if it wins on the primary metric.
Deliverable: A one-page product memo ending in a decision rather than a list of considerations.
Practice prompt ↗Practice prompt ↗04The places aggregate numbers lie
- Construct a Simpson's paradox numerically: two segments where the treatment wins within each segment yet loses overall, and identify the shift in segment weights that causes it.
- Take a heavy right-tailed quantity such as revenue per user and write why the mean is the wrong summary, which percentile you would report instead, and what a moving mean with a stable median tells you.
- Write your definition of a session for the product from day one, then name two real behaviours it misclassifies.
Deliverable: One page holding a worked Simpson's paradox table and a session definition with its two known failure cases.
Practice prompt ↗Practice prompt ↗Worked solution ↗05Technical maintenance, aimed at metrics
- Solve four timed SQL prompts that all end in a ratio metric, so the question of grain stays live in every answer.
- Compute a 95 percent confidence interval for a proportion on a small sample, and state why the normal approximation is unreliable when either np or n(1 minus p) falls below roughly 10, along with which interval you would use instead.
- Take one metric from your day-one tree, write the query that computes it correctly, then write the query that computes it wrong in the most plausible way and explain how you would notice.
Deliverable: Four solved prompts plus a matched correct and plausible-wrong query for one metric.
Practice prompt ↗Practice prompt ↗06Turning engineering work into data science stories
- Write three project stories as situation, decision, trade-off, outcome, each carrying one number and one thing you got wrong.
- For the story you will lead with, prepare an answer to "what would you do differently" that names a decision you made, not a constraint you were handed.
- Practise the sentence that reframes a systems project as a question project: the question the work answered, ahead of the pipeline it shipped.
Deliverable: Three written stories with the lead story delivered aloud and timed under four minutes.
Practice prompt ↗Practice prompt ↗07Mock case and gap list
- Run a 40-minute mock case with someone playing a product manager who pushes back on your metric choice, and record it.
- Listen back and mark every moment you proposed a solution before the success metric existed.
- Rewrite those moments as the question you should have asked, and rehearse the first 90 seconds of the case until scoping comes before solving.
Deliverable: A recorded case plus a rewritten opening 90 seconds.
Practice prompt ↗Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
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.
Why do you want to apply your data science skills to the public sector…
Why do you want to apply your data science skills to the public sector specifically?
Approach
- State the situation in two sentences and spend the rest on your reasoning.
- Quantify the outcome, including what you would not claim credit for.
- Name the disagreement or constraint, and how you resolved it with evidence.
Follow-up
- What did you decide not to do, and why?
- How did you know the outcome was caused by your change?
How do you handle situations where the data does not support a stakeho…
How do you handle situations where the data does not support a stakeholder's preferred outcome?
Approach
- Pick a story where you drove the decision, not one where you observed it.
- Close with what you would do differently, concretely.
- Quantify the outcome, including what you would not claim credit for.
Follow-up
- What would you do differently if you ran that project again?
- What did you decide not to do, and why?
Turn "how many people are we missing" into a scoped estimate
A programme director stops you and asks, "how many people are we missing?" You have fact_application, dim_constituent and dim_geography and nothing else. Before writing any SQL you owe her a written scope in thirty minutes: the question restated as something estimable, the denominator you will build and where it comes from, what the data cannot tell her, and a delivery date. Produce that scope note in under 300 words, plus the three clarifying questions whose answers would change the estimate.
Approach
- Restate the request as a coverage estimand before negotiating anything else: distinct constituent_id with status = 'approved' and decision_at inside a named program year, over a modelled count of eligible constituents in the same geography and year. Say out loud that the denominator cannot come from fact_application, because every row there belongs to someone who already found the system.
- Write the three questions whose answers move the number by more than the analysis will: which program_code and which rule_version_id define eligibility, which geo_level and vintage_year is the denominator drawn at, and whether "missing" means never created an application, created one but has submitted_at IS NULL, or was denied on a procedural denial_reason_code. Those are three different interventions and only the first is a pure outreach problem.
- Name the denominator pipeline concretely: survey microdata plus a microsimulation of the eligibility rule in force at that rule_version_id, with the income and household definitions stated because reported_income_cents in the administrative table is self-reported and heaps on round values.
- State the non-answers before they are asked for: the estimate is a count, not a list of people, and the gap cannot be attributed to a cause without a design.
- Commit to a date and offer an interim artefact on day one, the funnel from created_at to submitted_at to decision_at by channel, so the director has something concrete while the denominator is built.
Follow-up
- The director asks for the names of the people who are missing so outreach can call them. What do you say, and what could you legitimately produce instead?
- Your modelled eligible population comes out 40 percent above the applicant count. What checks do you run before anyone sees that number?
- 01
Why do you want to apply your data science skills to the public sector specifically?
- 02
How do you handle situations where the data does not support a stakeholder's preferred outcome?
- 03
A programme director stops you and asks, "how many people are we missing?" You have fact_application, dim_constituent and dim_geography and nothing else. Before writing any SQL you owe her a written scope in thirty minutes: the question restated as something estimable, the denominator you will build and where it comes from, what the data cannot tell her, and a delivery date. Produce that scope note in under 300 words, plus the three clarifying questions whose answers would change the estimate.
Is this an official The City Of Providence interview guide?
No. It is PracHub's own research and practice material for the Data Scientist role at The City Of Providence. Rounds and questions reflect what candidates have reported, not a process The City Of Providence has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗How long does the hiring process usually take?
While some roles move quickly, be prepared for a process that can span several weeks. It is common for government hiring processes to have longer lead times compared to the private sector.
PracHub interview research ↗What is the most common reason candidates are not selected?
The most frequent feedback relates to a lack of clear communication or an inability to articulate how their technical skills translate to the specific, often messy, challenges of municipal data.
PracHub interview research ↗Is there a coding test?
Yes, for many technical roles, you may be asked to complete a case study or a technical assessment to demonstrate your problem-solving process.
PracHub interview research ↗How should I prepare for the "culture fit" portion of the interview?
Focus on your interest in public service. Research current initiatives in The City Of Providence and be ready to discuss how your work could support those specific community goals.
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