← Meta Interview Insights

Meta·Data Scientist·Technical Phone Screen·Senior

Senior
May 2026

Summary

SQL-heavy Data Scientist screen at Meta, six questions deep, all centered on a single schema with users, shops, visibility events, and calls. The problems escalated fast from basic aggregations to window functions and bias discussion, which I was not fully prepared for.

Questions Asked (6)

Q1

Given a visibility_events table, write a query for a single day that returns each shop's unique viewer count and average dwell time (visible rows only), filtered to shops with at least 2 unique viewers, sorted by viewer count descending then shop_id ascending.

Product Analytics & MetricsData Modeling
Author's notes

Pretty standard GROUP BY / HAVING stuff.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

First, clarify the schema and definitions: what columns exist in visibility_events, how to identify unique viewers (e.g., user_id), how to compute dwell time (e.g., sum of visible durations), and what 'visible rows only' means (e.g., is_visible = true). Then write a query that filters to the target day and visible events, aggregates per shop to get unique viewer count and average dwell time, applies a HAVING clause for at least 2 unique viewers, and orders by viewer count descending then shop_id ascending.

Pro tip: Mention that you would validate the query on a small sample and check for edge cases like shops with zero visible events or null dwell times, and discuss how you'd handle timezone considerations for 'a single day'.

1. Clarify requirements and schema

Ask about the table schema, definitions of unique viewer, dwell time, and visibility. Confirm the date range and timezone for 'a single day'.

2. Filter and aggregate

Filter rows to the specified day and visible events. Group by shop_id and compute COUNT(DISTINCT viewer_id) and AVG(dwell_time) or SUM(dwell_time)/COUNT(DISTINCT viewer_id) depending on definition.

3. Apply HAVING and sorting

Use HAVING COUNT(DISTINCT viewer_id) >= 2 to filter shops. Order by unique viewer count DESC, then shop_id ASC.

4. Validate and optimize

Check for nulls, duplicates, and performance. Consider indexing on date and shop_id, and test on a sample.

Key Points to Mention

  • Use COUNT(DISTINCT viewer_id) for unique viewers, not COUNT(*).
  • Filter with WHERE on date and visibility flag before aggregation.
  • Use HAVING for the condition on aggregated unique viewer count.
  • Compute average dwell time correctly: AVG(dwell_time) or SUM(dwell_time)/COUNT(DISTINCT viewer_id) depending on business definition.
  • Handle timezone and date boundaries explicitly.
  • Consider performance: partition by date, use appropriate indexes.

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

Q2

Join the shops table to visibility events and compute, by category, the share of visible events that appeared in position 3 or above (top-of-feed). Round to 3 decimal places and sort descending.

Product Analytics & MetricsData Modeling
Author's notes

The CASE inside an AVG trick is cleaner than writing two subqueries and I knew that going in, so this one felt okay.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the schema and definitions: identify the join key between shops and visibility events, define 'visible events' and 'position 3 or above', and confirm the denominator is all visible events per category. Then write a SQL query that joins the tables, filters to visible events, computes the share of events with position <= 3 per category, rounds to 3 decimals, and sorts descending.

Pro tip: Always state your assumptions about the data model (e.g., event grain, join cardinality) before writing SQL—interviewers at Meta care more about your reasoning than perfect syntax. Also, mention that you'd validate the join doesn't duplicate events, as that could skew the share.

1. Clarify schema and definitions

Ask about the join key (e.g., shop_id), what constitutes a 'visible event', and how position is recorded. Confirm that 'position 3 or above' means position <= 3 (top of feed).

2. Define the metric and denominator

The share is (number of visible events with position <= 3) / (total number of visible events) per category. Ensure the denominator includes all visible events, not just those in top positions.

3. Write the SQL query

Join shops to visibility events on shop_id, filter for visible events, then group by category. Use conditional aggregation (e.g., SUM(CASE WHEN position <= 3 THEN 1 ELSE 0 END) / COUNT(*)) to compute the share, round to 3 decimals, and order by share descending.

4. Validate and interpret

Check for edge cases: categories with zero visible events, null positions, or duplicate events from the join. Discuss how the metric could inform product decisions (e.g., feed ranking effectiveness).

Key Points to Mention

  • Join key and cardinality (e.g., one-to-many between shops and events)
  • Definition of 'visible event' (e.g., event_type = 'visible' or is_visible = true)
  • Position filter: position <= 3 for top-of-feed
  • Conditional aggregation to compute share per category
  • Rounding to 3 decimal places and sorting descending
  • Handling NULLs or missing data in position or category

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

Q3

Using a window function, compute for each user the time gap in seconds between consecutive visible events on a given day. The last visible event per user should return NULL.

Product Analytics & MetricsAlgorithms & Data Structures
Author's notes

LEAD over a partition by user_id ordered by event_time, then EXTRACT the epoch difference.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Use a window function like LEAD or LAG partitioned by user and ordered by event timestamp to compare each event with the next. Compute the difference in seconds, and ensure the last event per user returns NULL by using LEAD without a default. Filter for visible events and the specific day before applying the window function.

Pro tip: Mention that you would handle ties in timestamps by adding a secondary sort key (e.g., event_id) to ensure deterministic ordering, and clarify that 'visible' events are defined by a flag or condition in the data.

1. Filter and scope the data

Restrict the dataset to the given day and only visible events (e.g., where is_visible = true). This reduces the data size and ensures the window function operates on the correct subset.

2. Order events within each user

Use PARTITION BY user_id ORDER BY event_timestamp (and a tiebreaker like event_id) to define the sequence of events for each user.

3. Apply window function to get next event time

Use LEAD(event_timestamp) OVER (PARTITION BY user_id ORDER BY event_timestamp) to retrieve the timestamp of the next visible event for each row. The last event will have NULL as the next timestamp.

4. Compute time gap in seconds

Calculate the difference between the next event timestamp and the current event timestamp, converting to seconds (e.g., using TIMESTAMPDIFF or EXTRACT(EPOCH) depending on SQL dialect). The last event will naturally yield NULL.

5. Validate and handle edge cases

Check for users with only one event (gap should be NULL) and ensure the result includes all users. Optionally, discuss how to handle ties or missing timestamps.

Key Points to Mention

  • Use of LEAD (or LAG) window function with PARTITION BY user_id and ORDER BY event_timestamp
  • Filtering for visible events and the specific day before applying the window function
  • Conversion of time difference to seconds using appropriate SQL functions (e.g., TIMESTAMPDIFF, EXTRACT(EPOCH))
  • Handling of the last event per user to return NULL (LEAD naturally does this)
  • Consideration of ties in timestamps and adding a tiebreaker for deterministic ordering
  • Performance implications: filtering before windowing to reduce data size

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

Q4

Bucket users by account age in days into four ranges, define an 'active' user as having at least one visible event with dwell >= 5 seconds today, and compute DAU, active users, and active rate per bucket using only today's data. Also explain one bias introduced by restricting the denominator to users seen today.

A/B Testing & ExperimentationProduct Analytics & MetricsData Modeling
Author's notes

The SQL part I handled fine after sketching the CASE statement for buckets.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the definitions and assumptions, especially what 'visible event' and 'dwell >= 5 seconds' mean, and how to bucket account age. Then outline the SQL or pseudocode logic to compute DAU, active users, and active rate per bucket using only today's data, and finally discuss the bias introduced by restricting the denominator to users seen today.

Pro tip: Mention that the active rate denominator should be DAU (users seen today) to match the question, but note that this measures 'engagement among today's users' rather than 'true active rate' across all users. Also, consider edge cases like users with no events today or account age exactly at bucket boundaries.

1. Clarify Definitions and Assumptions

Define 'visible event' (e.g., event_type = 'visible'), 'dwell >= 5 seconds' (e.g., dwell_time >= 5), and 'account age in days' (e.g., DATEDIFF(today, signup_date)). Confirm that only today's data is used and that DAU is the count of distinct users with any event today.

2. Bucket Users by Account Age

Create four buckets based on account age in days (e.g., 0-7, 8-30, 31-90, 90+). Ensure buckets are mutually exclusive and cover all possible ages. Use a CASE statement or equivalent to assign each user to a bucket.

3. Compute DAU per Bucket

For each bucket, count distinct users who had at least one event today (any event, not just visible). This gives DAU per bucket. Ensure you only include users seen today.

4. Compute Active Users per Bucket

For each bucket, count distinct users who had at least one visible event with dwell >= 5 seconds today. This is the numerator for active rate.

5. Calculate Active Rate and Discuss Bias

Active rate per bucket = active users / DAU per bucket. Then explain the bias: restricting the denominator to users seen today excludes users who were not active today, potentially overestimating engagement and introducing selection bias (e.g., survivorship bias).

Key Points to Mention

  • Definition of 'active' user: at least one visible event with dwell >= 5 seconds today.
  • DAU is defined as distinct users with any event today, not just active users.
  • Account age buckets should be mutually exclusive and cover all ages (e.g., 0-7, 8-30, 31-90, 90+).
  • Active rate = active users / DAU per bucket, using only today's data.
  • Bias: denominator restricted to users seen today leads to selection bias, as it excludes users who churned or were inactive today, inflating the active rate.
  • Potential edge cases: users with no events today are excluded from DAU, and account age exactly at bucket boundaries should be handled consistently.

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

Q5

Count distinct users who made or received at least one call on a given day. Write two versions: one using UNION and one using UNION ALL. Explain exactly when they produce different results on the sample data, and state the general rule for when UNION ALL is safe vs. when it double-counts.

Data ModelingTechnical Trade-offs
Author's notes

UNION deduplicates across the two sets so a user who both called and received still counts once.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the schema and the definition of 'distinct users' (caller and receiver IDs). Then write both queries: one using UNION to deduplicate user IDs across the two roles, and one using UNION ALL to concatenate them. Finally, explain the difference in results on sample data and state the general rule for when UNION ALL is safe versus when it double-counts.

Pro tip: Mention that UNION ALL is faster because it skips deduplication, but only safe if you can guarantee no user appears in both caller and receiver roles for the same day—or if you apply DISTINCT after the UNION ALL. This shows you understand performance trade-offs and data semantics.

1. Clarify schema and requirements

Confirm the table structure (e.g., call_date, caller_id, receiver_id) and that 'distinct users' means unique user IDs across both roles for the given day.

2. Write UNION version

Use a subquery to select caller_id and receiver_id for the given day, then apply UNION to combine and deduplicate, and finally COUNT(DISTINCT user_id) or COUNT(*) on the deduplicated set.

3. Write UNION ALL version

Use UNION ALL to concatenate caller_id and receiver_id without deduplication, then apply COUNT(DISTINCT user_id) to get the correct distinct count. Alternatively, if you skip the outer DISTINCT, you'll get a double-counted total.

4. Compare on sample data

Construct a small sample where a user appears as both caller and receiver on the same day. Show that UNION yields a lower count than UNION ALL without outer DISTINCT, and explain why.

5. State general rule

Explain that UNION ALL is safe when the two sets are guaranteed disjoint (no user in both roles) or when you apply DISTINCT afterward. Otherwise, it double-counts users who appear in both roles.

Key Points to Mention

  • UNION removes duplicates across the combined result set, while UNION ALL retains all rows including duplicates.
  • The correct distinct user count requires deduplication either via UNION or via COUNT(DISTINCT) after UNION ALL.
  • Double-counting occurs when a user is both a caller and a receiver on the same day, and you use UNION ALL without an outer DISTINCT.
  • UNION ALL is generally faster because it avoids the sort/hash deduplication step, but it may require additional processing if deduplication is needed later.
  • The general rule: UNION ALL is safe when the two sets are disjoint (e.g., caller and receiver IDs never overlap) or when you explicitly deduplicate afterward.
  • In practice, you might use UNION ALL for performance and then apply DISTINCT, but you must be aware of the trade-off between speed and correctness.

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

Q6

Using a window function, compute a 3-event moving average of dwell_seconds per shop ordered by event time, using ROWS BETWEEN 2 PRECEDING AND CURRENT ROW.

Product Analytics & MetricsAlgorithms & Data Structures
Author's notes

Bonus question, they said it was optional but then just waited.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

First, clarify the table schema and ensure event_time is sortable. Then, write a SQL query using AVG(dwell_seconds) OVER (PARTITION BY shop ORDER BY event_time ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) to compute the moving average. Finally, explain how the window frame handles edge cases like the first few events.

Pro tip: Mention that the moving average for the first two events per shop will be based on fewer than three events, and consider whether to filter them out or handle them separately. Also, note that if there are ties in event_time, the ordering might be non-deterministic, so adding a tiebreaker like event_id is prudent.

1. Clarify the data model

Ask about the table structure: column names, data types, and whether there are multiple events per shop. Confirm that event_time is a timestamp or sortable field.

2. Define the window specification

Specify PARTITION BY shop to compute per shop, ORDER BY event_time to sequence events, and the frame ROWS BETWEEN 2 PRECEDING AND CURRENT ROW to include the current and two preceding events.

3. Write the SQL query

Use AVG(dwell_seconds) OVER (PARTITION BY shop ORDER BY event_time ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg. Optionally, include other columns for context.

4. Address edge cases and validation

Discuss how the first two events per shop will have averages based on 1 or 2 events. Consider if you need to filter them out or handle them. Also, check for ties in event_time and add a tiebreaker if necessary.

5. Explain the output and use case

Describe the resulting column and how it can be used for analysis, such as smoothing out short-term fluctuations in dwell time per shop.

Key Points to Mention

  • Window functions: AVG() OVER with PARTITION BY and ORDER BY
  • Frame clause: ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
  • Handling of first two events per shop (partial windows)
  • Potential need for tiebreaker in ORDER BY (e.g., event_id)
  • Performance considerations: window functions can be expensive on large datasets
  • Difference between ROWS and RANGE for moving averages

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