← TikTok Interview Insights

TikTok·Data Scientist·Technical Phone Screen·Intermediate

Intermediate
May 2026

Summary

TikTok data scientist interview that was basically a gauntlet of SQL edge cases. Four parts, all technical, all interconnected across the same schema. The kind of session where you think you're done and then they ask you to redo it a different way.

Questions Asked (4)

Q1

Using a single grouped query joining orders to users by country, show COUNT(*), COUNT(amount), and COUNT(DISTINCT user_id) side by side. Explain how NULL values affect each count and why the join can cause double-counting issues.

Product Analytics & MetricsData ModelingTechnical Trade-offs
Author's notes

I knew COUNT(*) counts all rows including NULLs and COUNT(column) skips them, but I fumbled explaining why COUNT(DISTINCT user_id) matters when users have multiple orders.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by writing the SQL query: SELECT u.country, COUNT(*) AS total_rows, COUNT(o.amount) AS non_null_amounts, COUNT(DISTINCT o.user_id) AS distinct_users FROM orders o JOIN users u ON o.user_id = u.id GROUP BY u.country. Then explain how COUNT(*) counts all rows including NULLs, COUNT(amount) ignores NULLs, and COUNT(DISTINCT user_id) counts unique non-NULL user IDs. Finally, discuss how the join can cause double-counting if a user has multiple orders or if the join is not properly constrained, and how to detect and mitigate it.

Pro tip: Always clarify the grain of the result: COUNT(*) gives the number of order rows per country, not the number of users. Mention that if you need user-level metrics, you should aggregate before joining or use DISTINCT appropriately.

1. Write the SQL query

Construct a single grouped query joining orders to users on user_id, grouping by country, and selecting the three counts side by side.

2. Explain NULL handling in counts

Describe how COUNT(*) counts all rows, COUNT(amount) counts only non-NULL amounts, and COUNT(DISTINCT user_id) counts unique non-NULL user IDs.

3. Discuss double-counting from joins

Explain that joining orders to users can duplicate order rows if a user has multiple orders, leading to inflated COUNT(*) and COUNT(amount) if not careful.

4. Propose mitigation strategies

Suggest ways to avoid double-counting, such as aggregating orders before joining, using DISTINCT, or clarifying the grain of analysis.

Key Points to Mention

  • COUNT(*) includes NULLs and counts all rows, while COUNT(column) excludes NULLs.
  • COUNT(DISTINCT user_id) ignores NULL user_ids and counts unique non-NULL values.
  • Joining orders to users can cause row duplication if a user has multiple orders, inflating counts.
  • The grain of the result is order-level, not user-level, unless aggregated differently.
  • To avoid double-counting, aggregate orders per user before joining or use subqueries.
  • Always verify the join keys and cardinality to ensure correct results.

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

Q2

Per country, find the top 2 spenders by total order amount using ROW_NUMBER(). Then redo it with RANK() to handle ties. For country US specifically, which user IDs appear under each method and why do they differ?

Data ModelingTechnical Trade-offsProduct Analytics & Metrics
Author's notes

ROW_NUMBER assigns unique ranks so you always get exactly 2 rows per country even if there's a tie.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

First, write the SQL query using ROW_NUMBER() partitioned by country and ordered by total order amount descending, then filter for row numbers 1 and 2. Then, rewrite the query using RANK() and compare the results for the US, explaining that RANK() assigns the same rank to ties, so if there is a tie for second place, more than two users may appear. Finally, discuss the implications for business decisions and the trade-offs between the two functions.

Pro tip: Mention that the choice between ROW_NUMBER() and RANK() depends on whether you want to arbitrarily pick one user or include all tied users, and that this can affect metrics like user segmentation and incentive programs.

1. Understand the requirement

Clarify that the goal is to find the top 2 spenders per country based on total order amount, and that the difference between ROW_NUMBER() and RANK() matters when there are ties.

2. Write the ROW_NUMBER() query

Use a subquery or CTE to calculate total spend per user per country, then apply ROW_NUMBER() OVER (PARTITION BY country ORDER BY total_spend DESC) and filter for row_number <= 2.

3. Write the RANK() query

Replace ROW_NUMBER() with RANK() in the same query structure, and filter for rank <= 2.

4. Compare results for US

Execute both queries for country = 'US' and identify which user IDs appear under each method. Note any differences and the reasons (ties).

5. Explain the difference and implications

Discuss that ROW_NUMBER() arbitrarily breaks ties, while RANK() assigns the same rank to tied values, potentially including more than two users. Explain the business implications of each approach.

Key Points to Mention

  • Definition of ROW_NUMBER() and RANK() and how they handle ties.
  • The importance of PARTITION BY country and ORDER BY total order amount DESC.
  • The need to aggregate total order amount per user before ranking.
  • The potential for more than two users to appear when using RANK() if there is a tie for second place.
  • The business implications: ROW_NUMBER() gives a fixed number of users, while RANK() may include all tied users, affecting fairness and incentives.
  • The importance of communicating assumptions and the impact of tie-breaking on analytics.

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

Q3

Show how filtering orders with amount IS NULL in WHERE versus HAVING produces different results when trying to find users with at least 2 video_play events on a specific date who also have zero non-NULL orders.

Data ModelingRoot Cause AnalysisTechnical Trade-offs
Author's notes

This one actually tripped me up more than I expected.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

First, clarify the logical difference between WHERE and HAVING: WHERE filters rows before aggregation, while HAVING filters after aggregation. Then, construct a query that joins users, video_play events, and orders, and show how filtering orders with amount IS NULL in WHERE versus HAVING changes the result set, especially when counting orders. Finally, explain that to find users with zero non-NULL orders, you must ensure the join and filter conditions correctly exclude non-NULL orders without eliminating users who have no orders at all.

Pro tip: Emphasize that using WHERE amount IS NULL filters out non-NULL orders before aggregation, which can inadvertently remove users who have both NULL and non-NULL orders, whereas HAVING amount IS NULL after aggregation would incorrectly filter out users with any non-NULL orders. The correct approach often involves a LEFT JOIN and a condition like COUNT(non_null_orders) = 0.

1. Clarify the goal and data model

Restate the problem: find users with at least 2 video_play events on a specific date and zero non-NULL orders. Identify the relevant tables (users, events, orders) and their relationships.

2. Explain WHERE vs HAVING semantics

Describe that WHERE filters individual rows before grouping, while HAVING filters groups after aggregation. This distinction is crucial when filtering on aggregated conditions like order counts.

3. Construct the query with WHERE

Write a query that filters orders with amount IS NULL in WHERE, then aggregates. Show that this may exclude users who have non-NULL orders, but also may include users with no orders if using LEFT JOIN. Highlight potential pitfalls.

4. Construct the query with HAVING

Write a query that aggregates first, then applies HAVING amount IS NULL. Show that this is invalid because amount is not in GROUP BY, or if using MIN/MAX, it incorrectly filters groups with any non-NULL order.

5. Compare results and recommend correct approach

Contrast the two approaches, showing how they yield different user sets. Recommend using a LEFT JOIN and counting non-NULL orders (e.g., COUNT(o.amount) = 0) to correctly identify users with zero non-NULL orders.

Key Points to Mention

  • WHERE filters rows before aggregation; HAVING filters after aggregation.
  • Filtering amount IS NULL in WHERE removes non-NULL orders before counting, which can change the set of users.
  • Using HAVING amount IS NULL is invalid unless amount is aggregated or in GROUP BY.
  • To find users with zero non-NULL orders, use LEFT JOIN and COUNT(o.amount) = 0 or SUM(CASE WHEN o.amount IS NOT NULL THEN 1 ELSE 0 END) = 0.
  • The specific date filter for video_play events should be applied in WHERE before aggregation.
  • Ensure at least 2 video_play events by using HAVING COUNT(event) >= 2 or a subquery.

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

Q4

Compute average spend per active user (defined as someone with at least one event on a given date) by country, guarding against divide-by-zero and NULL amounts. Explain why you would use COALESCE and NULLIF here and what each one is actually doing.

Product Analytics & MetricsData ModelingTechnical Trade-offs
Author's notes

NULLIF turns a zero denominator into NULL so the division returns NULL instead of blowing up.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

First, clarify the metric definition: average spend per active user by country, where active users are those with at least one event on a given date. Then, write a SQL query that aggregates spend per user per date, filters to active users, and computes the average spend per country, using COALESCE to handle NULL amounts and NULLIF to prevent division by zero. Finally, explain the roles of COALESCE and NULLIF in ensuring robust calculations.

Pro tip: Mention that you would validate the metric by checking edge cases, such as days with zero active users or all NULL spends, and consider whether to use SUM(spend)/COUNT(DISTINCT user_id) or AVG(spend) depending on the granularity of the data.

1. Clarify the metric and assumptions

Define 'active user' as someone with at least one event on a given date, and 'average spend per active user' as total spend divided by number of active users, aggregated by country. Confirm whether spend is per event or per user per day, and how to handle NULLs.

2. Design the query structure

Use a subquery or CTE to filter events to active users per date, then aggregate spend per user per date, and finally compute the average per country. Ensure you group by country and date if needed, or overall if the question implies a single average per country.

3. Apply COALESCE and NULLIF

Use COALESCE(spend, 0) to treat NULL spend as zero, and NULLIF(COUNT(DISTINCT user_id), 0) to avoid division by zero. Explain that COALESCE replaces NULLs with a default, while NULLIF returns NULL if the denominator is zero, preventing errors.

4. Explain the rationale

Articulate that COALESCE ensures NULL amounts don't propagate as NULL in sums or averages, and NULLIF guards against divide-by-zero by turning a zero denominator into NULL, which results in NULL instead of an error.

5. Validate and discuss trade-offs

Mention potential pitfalls: using AVG(spend) after filtering active users might differ from SUM/COUNT if some users have multiple events. Also, consider performance implications of COUNT(DISTINCT) and whether to pre-aggregate.

Key Points to Mention

  • Definition of active user: at least one event on a given date.
  • COALESCE handles NULL spend amounts by replacing them with 0.
  • NULLIF prevents division by zero by converting a zero denominator to NULL.
  • Difference between AVG(spend) and SUM(spend)/COUNT(DISTINCT user_id) when users have multiple events.
  • Importance of filtering to active users before computing averages.
  • Edge cases: days with no active users, all NULL spends, and countries with no data.

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