← SIG (Susquehanna) Interview Insights

SIG (Susquehanna)·Data Scientist·Technical Phone Screen·Intermediate

IntermediatePrefer not to say
Jul 2026Remote

Summary

SQL-heavy technical screen for a data scientist role at SIG. Six questions back to back, all analytics SQL, no behavioral fluff. The problems ranged from basic conversion funnels to some genuinely tricky window function and cross-join completeness stuff.

Questions Asked (6)

Q1

For a fixed 7-day window, compute conversion rate (delivered / created) broken down by city and courier type. An order counts as delivered if status is 'delivered' and delivered_at is not null, regardless of when it was delivered.

Product Analytics & MetricsData Modeling
Author's notes

Straightforward on the surface but the 'regardless of when delivered' part tripped me up briefly.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clearly defining the 7-day window filter on the 'created_at' timestamp to scope the order cohort, then aggregate delivered orders (status = 'delivered' AND delivered_at IS NOT NULL) grouped by city and courier_type. Express the conversion rate as COUNT(delivered orders) / COUNT(total created orders) per group, handling potential division-by-zero edge cases.

Pro tip: Explicitly call out that 'delivered' status is evaluated regardless of when delivery occurred — this means you filter the window on created_at only, not delivered_at, which is a subtle but critical distinction that separates strong candidates from average ones.

1. Define the Time Window

Clarify the 7-day window by filtering orders where created_at falls within a specific date range (e.g., BETWEEN '2024-01-01' AND '2024-01-07'). Confirm whether the window is fixed historically or rolling, and use inclusive/exclusive bounds consistently.

2. Identify Delivered Orders

Within the scoped cohort, flag an order as delivered only when status = 'delivered' AND delivered_at IS NOT NULL. Note that the delivery event can occur outside the 7-day window — what matters is that the order was created within it.

3. Aggregate by City and Courier Type

Group the filtered dataset by city and courier_type, computing COUNT(*) for total created orders and SUM of the delivered flag for delivered orders. This gives the numerator and denominator needed for the conversion rate.

4. Compute Conversion Rate Safely

Calculate conversion_rate = delivered_count / NULLIF(created_count, 0) to avoid division-by-zero errors. Optionally multiply by 100 and round to a meaningful decimal precision for readability.

5. Validate and Interpret Results

Sanity-check the output by verifying that conversion rates fall between 0 and 1 (or 0–100%), and flag any city/courier_type combinations with very low order volumes where rates may be statistically unreliable. Consider adding a volume threshold or confidence note.

Key Points to Mention

  • Filter on created_at for the 7-day window — NOT delivered_at — since the question asks for orders created in that period
  • Dual condition for delivery: status = 'delivered' AND delivered_at IS NOT NULL (both conditions required to avoid counting erroneous records)
  • Use NULLIF or CASE WHEN to guard against division-by-zero when a city/courier_type bucket has zero created orders
  • GROUP BY city, courier_type to produce the full breakdown matrix across all combinations
  • Acknowledge that low-volume segments may produce noisy or misleading conversion rates and suggest a minimum volume filter
  • Optionally use a CTE or subquery structure to separate the window scoping logic from the aggregation for readability and maintainability

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

Q2

Generate a monthly volume report for the last 3 months showing orders created, orders delivered, and GMV (delivered count times a fixed rate) per courier type. Months with zero activity must still appear in the output.

Product Analytics & MetricsData Modeling
Author's notes

The zero-fill requirement is where people mess up.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by generating a complete spine of all (month, courier_type) combinations for the last 3 months using a cross join, then left-join actual order data onto that spine so zero-activity months are preserved. Aggregate delivered counts and created counts separately, then compute GMV as delivered_count multiplied by the fixed rate per courier type.

Pro tip: Explicitly call out the 'zero-activity month' requirement as a classic completeness trap — interviewers at quant firms like SIG use this to test whether you default to INNER JOINs (which silently drop missing periods) versus a deliberate spine-based approach with CROSS JOIN and COALESCE.

1. Define the Date Spine

Generate a reference table of the last 3 calendar months (e.g., using DATE_TRUNC + GENERATE_SERIES or a CTE with explicit month values) to ensure every month appears in the output regardless of activity.

2. Enumerate All Courier Types

Pull the distinct list of courier types from the reference/dimension table (or from the orders table itself) so you can cross join it with the date spine to create every (month, courier_type) combination.

3. Aggregate Order Metrics

Left-join the orders table onto the spine, grouping by month and courier type to compute COUNT of orders created and COUNT of orders with a delivered status, using COALESCE to replace NULLs with zero.

4. Calculate GMV

Multiply the delivered count by the fixed rate per courier type (either a constant or looked up from a rates table) to derive GMV, ensuring the rate is applied after the COALESCE so zero-activity rows yield GMV of 0.

5. Format and Validate Output

Order results by month and courier type, then sanity-check that every expected (month, courier_type) row is present and that aggregate totals align with known benchmarks or a simpler rollup query.

Key Points to Mention

  • Spine/scaffold pattern using CROSS JOIN between date series and courier types to guarantee zero-activity rows appear
  • LEFT JOIN from spine to orders table (not INNER JOIN) to avoid silently dropping missing periods
  • COALESCE(COUNT(...), 0) or SUM with COALESCE to convert NULLs to zero for created and delivered counts
  • DATE_TRUNC('month', order_date) for consistent monthly bucketing and handling of month boundaries
  • GMV calculation as delivered_count * fixed_rate, and whether the rate varies by courier type (clarifying assumption)
  • Filtering logic for 'last 3 months' — clarify whether it means the 3 most recent complete calendar months or rolling 90 days, and handle timezone consistency

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

Q3

For each user, find the first streak of 3 consecutive delivered biker orders. Any non-biker delivery breaks the streak; non-delivered orders are ignored entirely. Return the first and third order IDs and the time span between them in minutes.

Algorithms & Data StructuresData Modeling
Author's notes

This was the one that made me sweat.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Filter out non-delivered orders first, then use a window function to assign row numbers partitioned by user and ordered by time, resetting the sequence whenever a non-biker delivery is encountered. Identify consecutive biker-only streaks of length ≥ 3 and extract the first such streak per user, returning the 1st and 3rd order IDs along with the timestamp difference in minutes.

Pro tip: At SIG, precision in edge-case handling signals strong analytical thinking — explicitly address what happens when a user has multiple streaks of exactly 3 (return only the earliest one) and when non-delivered orders appear mid-streak (they are invisible, so two biker deliveries separated by a non-delivered order still count as consecutive).

1. Filter & Rank Delivered Orders

Remove all non-delivered orders from the dataset entirely, as they are irrelevant to streak calculation. Assign a row number (e.g., delivered_rank) partitioned by user_id and ordered by order timestamp to establish a clean sequence of delivered orders.

2. Flag Biker vs. Non-Biker Deliveries

Within the filtered delivered orders, add a boolean flag indicating whether each order was delivered by a biker. Any non-biker delivery resets the streak, so this flag is the key signal for grouping.

3. Assign Streak Groups

Use the classic 'gaps and islands' technique: compute delivered_rank minus a row number partitioned by user_id and biker_flag to create a group identifier. Rows with the same group ID and biker_flag = true form a consecutive biker streak.

4. Identify Valid Streaks of Length ≥ 3

Count the size of each streak group and filter to those with at least 3 orders. Within each qualifying streak, use ROW_NUMBER() to label positions 1, 2, 3... so you can pinpoint the 1st and 3rd orders.

5. Extract First Streak Per User & Compute Time Span

Rank the valid streaks per user by the timestamp of their first order and keep only rank = 1 (the earliest streak). Return the order_id of position 1, the order_id of position 3, and DATEDIFF or TIMESTAMPDIFF in minutes between their timestamps.

Key Points to Mention

  • Gaps-and-islands pattern using ROW_NUMBER() minus ROW_NUMBER() trick to identify consecutive groups without self-joins
  • Importance of filtering non-delivered orders before any ranking or grouping, not after, to avoid incorrect sequence numbering
  • Non-biker delivered orders reset the streak counter, so they must be included in the ranking step to correctly break groups
  • Using FIRST_VALUE() or self-joining on streak position to retrieve both the 1st and 3rd order IDs within the same streak
  • Handling ties or duplicate timestamps — clarify assumptions with the interviewer (e.g., use a tiebreaker like order_id)
  • Time span calculation using TIMESTAMPDIFF(MINUTE, ...) in MySQL or EXTRACT(EPOCH FROM ...) / 60 in PostgreSQL, and noting timezone considerations

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

Q4

Compute the 95th percentile delivery time in minutes for delivered orders created in the last 30 days, grouped by courier type. Also return the count per group.

Product Analytics & Metrics
Author's notes

Pretty standard once you know percentile_cont.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Approach this as a SQL aggregation problem requiring window functions or percentile functions, filtering for delivered orders within the last 30 days and grouping by courier type. Use a database-appropriate percentile function (e.g., PERCENTILE_CONT in standard SQL or APPROX_PERCENTILE in distributed systems) to compute the 95th percentile of delivery duration in minutes. Clearly define delivery time as the difference between delivery timestamp and order creation timestamp.

Pro tip: At a quant-driven firm like SIG, mention awareness of approximate vs. exact percentile functions — APPROX_PERCENTILE is faster on large datasets but trades off precision, which is a real engineering decision worth flagging. Also note that outliers (e.g., cancelled-then-redelivered orders) can skew percentile results and should be discussed.

1. Clarify the Schema & Definitions

Identify the relevant table(s) and columns: order creation timestamp, delivery timestamp, order status, and courier type. Confirm that 'delivery time' means the elapsed minutes between order creation and delivery completion.

2. Filter the Dataset

Apply WHERE conditions to restrict rows to orders with status = 'delivered' and where the order creation timestamp falls within the last 30 days (e.g., created_at >= CURRENT_DATE - INTERVAL '30 days').

3. Compute Delivery Duration

Calculate delivery time in minutes using timestamp arithmetic, such as EXTRACT(EPOCH FROM (delivered_at - created_at)) / 60 in PostgreSQL or DATEDIFF('minute', created_at, delivered_at) in other dialects.

4. Apply Percentile & Count Aggregations

Group by courier_type and apply PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY delivery_minutes) for the 95th percentile, along with COUNT(*) for the group size.

5. Validate & Interpret Results

Check for NULL delivery timestamps or negative durations that could indicate data quality issues, and briefly interpret what a high 95th percentile for a specific courier type would mean operationally.

Key Points to Mention

  • Use of PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY ...) for exact percentile calculation in standard SQL
  • Distinction between exact percentile functions and approximate alternatives (APPROX_PERCENTILE) for scalability on large datasets
  • Proper timestamp arithmetic to convert delivery duration into minutes, accounting for SQL dialect differences
  • Filtering on both order status ('delivered') and creation date within the last 30 days to ensure correct scope
  • Handling of data quality issues such as NULL delivered_at timestamps, duplicate records, or negative delivery times
  • Business interpretation: how the 95th percentile (tail latency) is more actionable than the mean for identifying worst-case customer experiences

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

Q5

Extend the monthly volume report to include city as a dimension, ensuring every combination of city (from both users and couriers), courier type, and month appears in the output even with zero orders.

Data ModelingProduct Analytics & Metrics
Author's notes

Harder version of task B.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by identifying all distinct cities from both the users and couriers tables using a UNION to create a complete city dimension, then cross join it with all courier types and all months to build a complete spine of combinations. Finally, left join the actual order data onto this spine so that missing combinations naturally appear as zero rather than being omitted.

Pro tip: Explicitly calling out that cities must be sourced from BOTH users and couriers tables (not just one) signals you understand real-world data asymmetry — cities may have couriers but no users yet, or vice versa — which is the kind of edge case that separates strong analysts from average ones.

1. Build the Complete City Dimension

Use UNION (not UNION ALL) on the city column from both the users and couriers tables to get every distinct city that appears in either source, eliminating duplicates.

2. Generate the Full Spine via CROSS JOIN

Cross join the city dimension with all distinct courier types and all relevant months (e.g., from a date dimension or derived from the orders table) to produce every possible combination as the reporting spine.

3. Aggregate Actual Order Data

Separately aggregate the orders table by city, courier type, and month to get real volume counts, keeping this as a subquery or CTE before joining.

4. Left Join Actuals onto the Spine

Left join the aggregated order data onto the full spine using city, courier type, and month as join keys, then use COALESCE to replace NULL order counts with zero.

5. Validate and Sanity-Check Output

Verify the total row count equals cities × courier_types × months, and confirm that the sum of the new report's volume matches the original report's total to ensure no data was lost or duplicated.

Key Points to Mention

  • Using UNION (not UNION ALL) across users and couriers tables to deduplicate cities from both sources
  • CROSS JOIN to generate a complete Cartesian product of all dimension combinations (city × courier type × month)
  • LEFT JOIN from the spine to actuals — not the other way around — to preserve zero-volume rows
  • COALESCE(order_count, 0) to convert NULLs from unmatched rows into meaningful zero values
  • Ensuring the month dimension covers a consistent, pre-defined range rather than only months with activity
  • Row count and volume sum validation to confirm the spine is complete and aggregation is correct

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

Q6

Find all orders created on or before a cutoff timestamp that were not delivered within 2 hours of creation. Include orders where delivered_at is null or where it exceeds the 2-hour threshold. Return a boolean column flagging these stale orders.

Product Analytics & MetricsRoot Cause Analysis
Author's notes

Easy one to end on.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Approach this as a filtering and derived-column problem: first scope the dataset to orders at or before the cutoff timestamp, then compute the delivery duration and apply the two-hour threshold logic. Handle NULL delivered_at values explicitly using COALESCE or IS NULL checks, and surface the result as a boolean flag column for downstream use.

Pro tip: At a quant-driven firm like SIG, interviewers appreciate when you proactively mention edge cases such as timezone normalization, clock skew between systems, or whether 'created_at' and 'delivered_at' live in the same timezone — it signals production-level thinking beyond just writing correct SQL.

1. Clarify the Cutoff and Schema

Confirm the data types of created_at and delivered_at (TIMESTAMP vs. DATETIME) and how the cutoff value is supplied — as a parameter, a variable, or a hardcoded literal. This prevents silent type-mismatch errors.

2. Filter Orders Up to the Cutoff

Use a WHERE clause with created_at <= :cutoff_timestamp to scope the result set before applying any delivery logic, keeping the query efficient and the intent clear.

3. Compute Delivery Duration

Calculate the elapsed time between created_at and delivered_at using TIMESTAMPDIFF (MySQL), EXTRACT(EPOCH FROM ...) (PostgreSQL), or DATEDIFF with appropriate units, depending on the SQL dialect in use.

4. Apply the Stale-Order Condition

Flag an order as stale when delivered_at IS NULL OR the computed duration exceeds 7200 seconds (2 hours). Use a CASE WHEN expression or a boolean cast to produce the flag column, e.g., CASE WHEN delivered_at IS NULL OR delivery_seconds > 7200 THEN TRUE ELSE FALSE END AS is_stale.

5. Validate and Discuss Extensions

Sanity-check the output by checking counts of stale vs. non-stale orders and verifying a few NULL rows manually. Mention how this flag could feed into a root-cause analysis — e.g., joining with courier or region tables to identify systemic delays.

Key Points to Mention

  • Explicit NULL handling: delivered_at IS NULL must be treated as a stale order independently of the duration check, using OR logic rather than relying on NULL arithmetic returning NULL.
  • SQL dialect awareness: timestamp arithmetic syntax differs across engines (TIMESTAMPDIFF in MySQL, EPOCH extraction in PostgreSQL, DATEADD/DATEDIFF in SQL Server) — state your assumption or ask.
  • Boolean column representation: some SQL dialects lack a native BOOLEAN type; clarify whether to return TRUE/FALSE, 1/0, or a string flag depending on the downstream consumer.
  • Cutoff parameterization: using a bind parameter or CTE variable for the cutoff timestamp makes the query reusable and avoids hardcoding, which is important for scheduled pipelines.
  • Timezone consistency: confirm both timestamps are stored in the same timezone (ideally UTC) to avoid incorrect duration calculations across DST boundaries.
  • Downstream analytical value: the boolean flag enables easy aggregation (SUM(is_stale), AVG(is_stale)) for KPI dashboards or root-cause slicing by region, product category, or courier.

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