← Amazon Interview Insights

Amazon·Data Scientist·Technical Phone Screen·Intermediate

IntermediatePrefer not to say
Jun 2026Remote

Summary

SQL-heavy technical screen for a Data Scientist role at Amazon. Every question was hands-on MySQL with specific schema and expected output shapes, no fluff, no behavioral stuff at all. Felt more like a database engineer interview than a data science one, which I wasn't fully prepared for.

Questions Asked (5)

Q1

Explain LEFT JOIN, RIGHT JOIN, FULL OUTER JOIN, and CROSS JOIN in terms of set semantics and duplicate handling. Then write each join type across two given tables (emulating FULL OUTER JOIN in MySQL), give exact row counts, list the first three rows in a specified order, and explain how NULLs affect equality joins.

Data ModelingTechnical Trade-offs
Author's notes

This is where I lost the most time.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by defining each join type in terms of set operations (inner, left outer, right outer, full outer, cross) and how duplicates are preserved. Then, for the given tables, compute the exact row counts and first three rows for each join, emulating FULL OUTER JOIN in MySQL using UNION of LEFT and RIGHT JOINs. Finally, explain how NULLs affect equality joins, emphasizing that NULL = NULL is unknown, so rows with NULLs in join keys are excluded from inner joins and may appear only in outer joins.

Pro tip: When emulating FULL OUTER JOIN in MySQL, use UNION (not UNION ALL) to avoid duplicate rows, but be aware that if you need to preserve duplicates from both sides, you must use UNION ALL with careful handling of NULLs. Also, always clarify the order of rows by specifying an ORDER BY clause; otherwise, row order is not guaranteed.

1. Define join types with set semantics

Explain each join as a set operation: INNER JOIN = intersection, LEFT JOIN = left table plus matching right rows (nulls for non-matches), RIGHT JOIN = right table plus matching left rows, FULL OUTER JOIN = union of left and right with nulls for non-matches, CROSS JOIN = Cartesian product. Mention that duplicates are preserved based on the multiplicity of matching rows.

2. Compute row counts for given tables

For the two tables, calculate the number of rows returned by each join type. For INNER JOIN, count matching pairs; for LEFT/RIGHT, add non-matching rows from the respective side; for FULL OUTER, sum matches and non-matches from both sides; for CROSS JOIN, multiply row counts.

3. Emulate FULL OUTER JOIN in MySQL

Write a query using LEFT JOIN UNION RIGHT JOIN (or UNION ALL with deduplication) to simulate FULL OUTER JOIN. Ensure that rows appearing in both joins are not duplicated, typically by using UNION.

4. List first three rows in specified order

Apply the given ORDER BY clause to each join result and list the first three rows. Be precise about column values, including NULLs where applicable.

5. Explain NULL impact on equality joins

Describe that NULL represents unknown, so NULL = NULL evaluates to unknown (not true), meaning rows with NULL in join columns do not match in equality joins. They are excluded from INNER JOIN and appear only in outer joins as unmatched rows.

Key Points to Mention

  • Set semantics: INNER JOIN as intersection, LEFT/RIGHT as outer joins preserving one side, FULL OUTER as union, CROSS as Cartesian product.
  • Duplicate handling: duplicates are preserved based on matching rows; CROSS JOIN produces all combinations, potentially many duplicates.
  • Row count formulas: INNER = matches, LEFT = matches + left non-matches, RIGHT = matches + right non-matches, FULL = matches + left non-matches + right non-matches, CROSS = n*m.
  • MySQL FULL OUTER JOIN emulation using UNION of LEFT and RIGHT JOINs, with caution about duplicates.
  • NULL behavior: NULLs are not equal to each other, so they never match in equality joins; they appear as unmatched in outer joins.
  • Ordering: Always specify ORDER BY to determine row order; without it, order is undefined.

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

Q2

For each of five given SELECT queries over a table with GROUP BY, HAVING, and aliases, determine whether the query executes or errors under MySQL's ONLY_FULL_GROUP_BY mode, and if it errors, state the exact reason.

Data ModelingTechnical Trade-offs
Author's notes

Q4 is the sneaky one.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

For each query, first identify all non-aggregated columns in the SELECT, HAVING, and ORDER BY clauses, then check if they are functionally dependent on the GROUP BY columns or appear in the GROUP BY list. If not, the query will error under ONLY_FULL_GROUP_BY, and you should state the exact reason (e.g., 'column X is not in GROUP BY and is not functionally dependent').

Pro tip: Mention that MySQL can detect functional dependency when the GROUP BY includes a primary key or unique key, so columns from the same table may be allowed even if not explicitly grouped. Also, note that aliases in HAVING are allowed, but aliases in GROUP BY are not.

1. Parse the query structure

Identify the SELECT list, FROM clause, WHERE, GROUP BY, HAVING, and ORDER BY. Note any aliases defined in SELECT.

2. List non-aggregated columns

Collect all columns that are not inside aggregate functions (e.g., SUM, COUNT, AVG) from SELECT, HAVING, and ORDER BY.

3. Check GROUP BY compliance

For each non-aggregated column, verify if it appears in the GROUP BY clause or is functionally dependent on the GROUP BY columns (e.g., if GROUP BY includes a primary key).

4. Determine error condition

If any non-aggregated column is not in GROUP BY and not functionally dependent, the query errors under ONLY_FULL_GROUP_BY. State the exact column and reason.

5. Consider alias usage

Check if aliases are used in GROUP BY (not allowed) or HAVING (allowed). Note that aliases in GROUP BY cause a syntax error, not an ONLY_FULL_GROUP_BY error.

Key Points to Mention

  • ONLY_FULL_GROUP_BY requires all non-aggregated columns in SELECT, HAVING, and ORDER BY to be functionally dependent on GROUP BY columns.
  • Functional dependency is recognized when GROUP BY includes a primary key or unique key of the table.
  • Aliases defined in SELECT can be used in HAVING but not in GROUP BY.
  • Aggregate functions like COUNT, SUM, AVG, MAX, MIN are exempt from the rule.
  • The exact error message is 'Expression #N of SELECT list is not in GROUP BY clause and contains nonaggregated column ... which is not functionally dependent on columns in GROUP BY clause'.
  • If a column is from a table that is not in the GROUP BY and not functionally dependent, it will cause an error.

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

Q3

Write a single query returning every user with their order count and total spend in dollars (two decimal places), including users with zero orders, sorted by total spend descending then user_id ascending.

Product Analytics & MetricsData Modeling
Author's notes

Standard LEFT JOIN with COUNT and SUM, COALESCE the nulls to 0.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the schema and defining 'total spend' as the sum of order amounts, then use a LEFT JOIN from users to orders to include zero-order users, and aggregate with COUNT and SUM, applying COALESCE for nulls. Finally, format the total spend to two decimal places and sort by total spend descending, then user_id ascending.

Pro tip: Mention that COUNT(o.order_id) correctly returns 0 for users with no orders, while COUNT(*) would incorrectly return 1; also, use COALESCE(SUM(o.amount), 0) to avoid nulls. This shows attention to detail and understanding of SQL semantics.

1. Clarify schema and definitions

Ask about the tables involved (e.g., users, orders) and confirm that 'total spend' means the sum of order amounts. Ensure you know the column names for user ID, order ID, and order amount.

2. Choose the right join

Use a LEFT JOIN from users to orders to include all users, even those with zero orders. This ensures no user is omitted.

3. Aggregate and handle nulls

Use COUNT(o.order_id) to count orders (returns 0 for no orders) and COALESCE(SUM(o.amount), 0) to get total spend, replacing nulls with 0. Apply ROUND(..., 2) to format to two decimal places.

4. Group and sort

Group by user_id (and any other selected user columns). Sort by total spend descending, then user_id ascending to meet the ordering requirement.

5. Write and validate the query

Compose the final SQL query, ensuring correct syntax and aliases. Verbally validate with edge cases (e.g., user with no orders) to demonstrate thoroughness.

Key Points to Mention

  • LEFT JOIN to include users with zero orders
  • COUNT(o.order_id) vs COUNT(*) for correct order count
  • COALESCE or IFNULL to handle null sums for zero-order users
  • ROUND function to format total spend to two decimal places
  • GROUP BY user_id and any other selected user columns
  • ORDER BY total_spend DESC, user_id ASC

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

Q4

Using only integer arithmetic (no loops or UDFs), write a SELECT that converts a duration_seconds column into a human-readable string in the format 'X days Y hours Z minutes W seconds'.

Data ModelingAlgorithms & Data Structures
Author's notes

Mod and integer division chained together.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Use integer division and modulo operations to extract days, hours, minutes, and seconds from the total seconds. Then concatenate these values with the appropriate labels using string functions, ensuring the format matches exactly. Avoid loops and UDFs by leveraging built-in arithmetic and string functions.

Pro tip: Mention that you would handle edge cases like zero seconds or negative values (if applicable) and that the solution should be efficient and portable across SQL dialects. Also, note that using modulo on the original value after division ensures correct remainders.

1. Extract days

Compute the number of whole days by dividing duration_seconds by 86400 (seconds in a day) using integer division.

2. Extract hours

Compute the remaining seconds after removing days, then divide by 3600 to get hours. Use modulo to get the remainder.

3. Extract minutes

Compute the remaining seconds after removing days and hours, then divide by 60 to get minutes. Use modulo to get the remainder.

4. Extract seconds

The remaining seconds after removing days, hours, and minutes is the seconds component. Use modulo 60 on the original value after previous extractions.

5. Format and concatenate

Convert each integer to string and concatenate with labels ' days ', ' hours ', ' minutes ', ' seconds' to produce the final string.

Key Points to Mention

  • Integer division and modulo operations to break down seconds into components.
  • Constants: 86400 seconds per day, 3600 seconds per hour, 60 seconds per minute.
  • Use of CAST or CONVERT to convert integers to strings for concatenation.
  • Avoidance of loops and UDFs by using pure SQL arithmetic and string functions.
  • Handling of edge cases such as zero duration or negative values (if applicable).
  • Portability across SQL dialects (e.g., using standard SQL functions).

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

Q5

Using a sessions table with login and logout timestamps, write a query returning session duration as both a TIME value and an integer seconds count using TIMEDIFF and TIME_TO_SEC, excluding rows where logout_at is NULL. Also explain how the query changes if NULL logout_at should be treated as the current time.

Data ModelingTechnical Trade-offs
Author's notes

Filtering NULLs is just WHERE logout_at IS NOT NULL.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by writing a straightforward SQL query that selects session duration using TIMEDIFF(logout_at, login_at) and converts it to seconds with TIME_TO_SEC, filtering out NULL logout_at. Then, explain how to handle NULL logout_at by substituting the current timestamp with COALESCE or IFNULL, and discuss the implications for open sessions and performance.

Pro tip: Mention that using COALESCE(logout_at, NOW()) treats open sessions as ongoing, which is useful for real-time analytics but may lead to inconsistent results if not handled carefully in reporting. Also, note that TIMEDIFF returns a TIME value limited to 838:59:59, so for very long sessions, consider using TIMESTAMPDIFF for seconds directly.

1. Clarify requirements and assumptions

Confirm that the sessions table has login_at and logout_at columns, and that duration should be calculated only for completed sessions. Ask if there are any edge cases like negative durations or timezone considerations.

2. Write the base query for non-NULL logout_at

Use SELECT TIMEDIFF(logout_at, login_at) AS duration_time, TIME_TO_SEC(TIMEDIFF(logout_at, login_at)) AS duration_seconds FROM sessions WHERE logout_at IS NOT NULL. This excludes open sessions.

3. Modify for NULL logout_at as current time

Replace logout_at with COALESCE(logout_at, NOW()) in both TIMEDIFF and TIME_TO_SEC, and remove the WHERE clause. This treats open sessions as ongoing up to the current moment.

4. Discuss trade-offs and alternatives

Explain that using NOW() makes the query non-deterministic and may impact performance on large tables. Mention alternatives like TIMESTAMPDIFF(SECOND, login_at, COALESCE(logout_at, NOW())) for direct seconds and avoiding TIME range limits.

5. Validate and test

Suggest testing with sample data including NULLs and long durations to ensure correctness, and consider indexing login_at and logout_at for performance.

Key Points to Mention

  • Use of TIMEDIFF and TIME_TO_SEC functions as specified.
  • Filtering with WHERE logout_at IS NOT NULL for completed sessions.
  • Handling NULLs with COALESCE or IFNULL to substitute current time.
  • Implications of using NOW() for real-time vs. batch processing.
  • Potential limitation of TIME type (max 838 hours) and alternative TIMESTAMPDIFF.
  • Performance considerations and indexing on timestamp columns.

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