← Upstart Interview Insights

Upstart·Data Scientist·Technical Phone Screen·Intermediate

Intermediate
Jul 2026

Summary

SQL-heavy technical screen for a Data Scientist role at Upstart. Three prompts all built around the same two-table schema, escalating from aggregation to attribution to a filtered count. The edge cases were the real test, not the queries themselves.

Questions Asked (3)

Q1

Given a table of marketing touches, compute for each calendar month the average number of touches per company, where the denominator is only companies that had at least one touch in that month (not all companies in the dataset).

Product Analytics & MetricsData Modeling
Author's notes

The denominator scoping is where people slip up.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Break the problem into two aggregation steps: first count the number of touches per company per month, then average those counts across only the companies that appear in each month. Emphasize that the denominator is the count of distinct active companies in that month, not the total company universe, which is the key nuance of this question.

Pro tip: Explicitly call out the denominator distinction upfront — interviewers at analytics-focused companies like Upstart use this type of question to test whether you read requirements carefully and avoid the common mistake of dividing by the total number of companies in the dataset.

1. Clarify the Schema and Requirements

Confirm the table structure (e.g., columns like company_id, touch_date, touch_id) and restate the key constraint: the denominator is only companies with at least one touch in that specific month, not all companies ever recorded.

2. Extract the Calendar Month

Truncate or format the touch_date to a year-month grain (e.g., DATE_TRUNC('month', touch_date) in SQL) so all touches can be grouped by calendar month.

3. Aggregate Touches per Company per Month

Group by (month, company_id) and COUNT the number of touches to produce a row for each company-month combination with its touch count — this naturally filters to only active companies.

4. Compute the Monthly Average

Wrap the previous result in an outer query that groups by month and takes AVG(touch_count); since every company in this intermediate table had at least one touch, the denominator is automatically correct.

5. Validate and Sanity-Check the Result

Verify the output by spot-checking a single month manually, and consider edge cases such as months with only one active company or ties in touch counts to ensure the logic holds.

Key Points to Mention

  • The critical denominator distinction: average is over active companies in that month only, not the full company universe
  • Using a two-level aggregation pattern: inner GROUP BY (month, company_id) then outer GROUP BY (month) with AVG
  • DATE_TRUNC or equivalent function to normalize dates to calendar month granularity
  • Why COUNT(*) vs COUNT(DISTINCT touch_id) matters depending on whether duplicate touch records are possible
  • The intermediate CTE or subquery approach for readability and auditability of the logic
  • Potential follow-up extensions such as weighting by company size or computing median instead of mean

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q2

For each company that converted, find the last marketing touch at or before the conversion timestamp. Return the company, conversion time, last touch time, channel, campaign, and a flag for whether the touch and conversion happened in the same calendar month. Exclude companies with no prior touches.

Data ModelingProduct Analytics & Metrics
Author's notes

The tie-break rule tripped me up a bit.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Approach this as a last-touch attribution problem by joining a conversions table to a marketing touches table using a non-equi join (touch timestamp <= conversion timestamp), then use a window function like ROW_NUMBER() or LAST_VALUE() partitioned by company to isolate the most recent touch before conversion. Finally, compute the same-month flag using date truncation or EXTRACT functions on both timestamps, and filter out companies with no prior touches using an INNER JOIN or EXISTS clause.

Pro tip: Mention that in production you'd also want to handle edge cases like ties (two touches at the exact same timestamp) by adding a secondary sort key such as touch_id, and consider indexing strategies on the timestamp columns to avoid a full cross-join scan on large datasets — this signals you think beyond just correctness to performance and reliability.

1. Identify & Join Source Tables

Start by clarifying the schema — a conversions table (company, conversion_timestamp) and a marketing_touches table (company, touch_timestamp, channel, campaign). Perform a JOIN between them on company where touch_timestamp <= conversion_timestamp to get all eligible touches per conversion.

2. Rank Touches to Find the Last One

Apply ROW_NUMBER() OVER (PARTITION BY company ORDER BY touch_timestamp DESC) to rank all qualifying touches per company, so rank = 1 identifies the most recent touch at or before conversion. Alternatively, use a correlated subquery with MAX(touch_timestamp) for the same result.

3. Filter to Last Touch Only

Wrap the ranked query in a CTE or subquery and filter WHERE row_num = 1 to retain only the last touch record per company, ensuring one output row per converted company.

4. Compute the Same-Month Flag

Add a boolean/integer flag column using DATE_TRUNC('month', conversion_timestamp) = DATE_TRUNC('month', touch_timestamp) — or equivalent EXTRACT(YEAR/MONTH) comparisons — to indicate whether both events fell in the same calendar month.

5. Exclude Companies with No Prior Touches

Because the JOIN is on touch_timestamp <= conversion_timestamp, companies with zero qualifying touches will naturally be excluded by an INNER JOIN; explicitly call this out to show awareness that LEFT JOIN would incorrectly include them with NULL touch data.

Key Points to Mention

  • Non-equi join condition (touch_timestamp <= conversion_timestamp) to scope eligible touches before conversion
  • Window function ROW_NUMBER() or MAX() subquery to isolate the single last touch per company
  • Use of CTEs for readability and modularity, separating the ranking logic from the final SELECT
  • DATE_TRUNC or EXTRACT for the same-calendar-month flag, and awareness of timezone handling if timestamps are timezone-aware
  • INNER JOIN semantics naturally excluding companies with no prior touches, versus the pitfall of using LEFT JOIN
  • Edge case handling: ties at the same touch_timestamp resolved by a secondary sort key, and performance considerations for large tables

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q3

Count the distinct companies where the last touch and conversion fall in the same calendar month. As a follow-up, also produce a version that additionally requires the gap between conversion and last touch to be 45 days or fewer.

Product Analytics & MetricsA/B Testing & Experimentation
Author's notes

Pretty much a wrapper around the previous query.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the schema (e.g., a touches/events table with company_id, touch_timestamp, conversion_timestamp) and then write a SQL query that extracts the calendar month from both the last-touch date and the conversion date, comparing them for equality. For the follow-up, add a DATEDIFF condition (≤ 45 days) alongside the same-month filter, and use COUNT(DISTINCT company_id) in both cases to avoid double-counting.

Pro tip: Explicitly call out edge cases such as companies with multiple conversions or multiple last touches — clarify whether 'last touch' means the most recent touch before conversion per company, and whether you need to handle NULL conversion dates — this signals production-level SQL thinking that interviewers at data-driven fintechs like Upstart reward.

1. Clarify Schema & Definitions

Ask about the table structure (columns like company_id, touch_date, conversion_date) and confirm what 'last touch' means — the maximum touch timestamp per company, or the touch immediately preceding conversion. Also confirm whether a company can have multiple conversions.

2. Identify the Last Touch per Company

Use a subquery or CTE with MAX(touch_date) grouped by company_id (and optionally by conversion_id) to isolate the single last-touch record for each company before writing the main filter logic.

3. Apply the Same-Month Filter

Compare the calendar month and year of the last touch and conversion dates using DATE_TRUNC('month', last_touch_date) = DATE_TRUNC('month', conversion_date) (or YEAR/MONTH extraction), then wrap with COUNT(DISTINCT company_id).

4. Add the 45-Day Gap Constraint (Follow-up)

Extend the WHERE clause with DATEDIFF(day, last_touch_date, conversion_date) BETWEEN 0 AND 45 (or ABS version if direction is ambiguous), keeping the same-month condition, and re-run COUNT(DISTINCT company_id) for the stricter result.

5. Sanity-Check & Interpret Results

Briefly discuss how you would validate the output — for example, checking that the 45-day version always returns a count ≤ the same-month-only version — and mention what the metric could mean for Upstart's attribution or funnel analysis.

Key Points to Mention

  • Use COUNT(DISTINCT company_id) to avoid inflating counts when a company has multiple touches or conversions
  • DATE_TRUNC or EXTRACT(YEAR/MONTH) for robust same-calendar-month comparison that handles year boundaries correctly
  • CTE or subquery to first compute the last touch per company before applying filters, keeping the logic readable and correct
  • DATEDIFF direction and sign — clarify whether conversion always comes after last touch and handle potential negative gaps or NULLs
  • Attribution context — explain why same-month and short-gap filters matter for measuring marketing channel effectiveness in a lending/fintech funnel
  • Mention that the 45-day constraint is a subset of the same-month constraint only when touches and conversions are within the same month, so results may differ when months span more than 45 days

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.