← Affirm Interview Insights

Affirm·Software Engineer·Technical Phone Screen·Senior

Senior
May 2026

Summary

SQL-heavy technical screen for a Data Engineer role at Affirm. Three questions, all SQL, all with a window function angle. Not a casual warm-up round.

Questions Asked (3)

Q1

Using a CTE, write a query to find total successful revenue and distinct active customers per region for the last three full calendar months. Include a window function to rank regions by revenue within each month. Output should include month (YYYY-MM), region, revenue, active_customers, and rank.

Product Analytics & MetricsData Modeling
Author's notes

This took me longer than it should have to set up the month truncation logic correctly.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the schema and definitions: successful revenue likely comes from a transactions table with a status column, and active customers are distinct customers with successful transactions in the period. Use a CTE to filter to the last three full calendar months, aggregate revenue and distinct customers per month and region, then apply a window function to rank regions by revenue within each month.

Pro tip: Explicitly state your assumptions about the data model (e.g., transaction statuses, date column, customer ID) and mention that you'd validate the 'last three full calendar months' logic against the current date to avoid partial months. This shows you think about data quality and edge cases.

1. Clarify definitions and schema

Ask or state assumptions about the tables: which table holds transactions, what column indicates success (e.g., status = 'success'), the date column, and how customers are identified. Define 'active customer' as a distinct customer with at least one successful transaction in the month.

2. Filter to last three full calendar months

Use a date filter to include only complete months. For example, if today is 2024-07-15, the last three full months are April, May, June 2024. This can be done with a WHERE clause on the transaction date.

3. Aggregate revenue and distinct customers per month and region

In a CTE, group by month (formatted as YYYY-MM) and region, summing revenue and counting distinct customer IDs. Ensure you only include successful transactions.

4. Apply window function to rank regions by revenue within each month

In the outer query, use RANK() or DENSE_RANK() OVER (PARTITION BY month ORDER BY revenue DESC) to assign a rank to each region within its month.

5. Select and order final output

Output month, region, revenue, active_customers, and rank. Order by month and rank for readability.

Key Points to Mention

  • Use of CTE for readability and modularity
  • Filtering to last three full calendar months (not including current partial month)
  • Defining 'successful revenue' (e.g., status = 'success' or 'completed')
  • Counting distinct active customers per region per month
  • Window function (RANK or DENSE_RANK) partitioned by month and ordered by revenue descending
  • Handling of regions with zero revenue or no customers (e.g., using LEFT JOIN or ensuring inclusion)

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

Q2

For each customer, return their top three successful transactions by amount. Ties in amount should be broken by the earlier transaction timestamp. Return customer_id, transaction_id, amount, transaction_ts, and the per-customer rank.

Algorithms & Data StructuresData Modeling
Author's notes

Easiest of the three.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Use a window function (ROW_NUMBER) partitioned by customer_id, ordered by amount DESC and transaction_ts ASC, to rank successful transactions. Then filter to ranks 1-3 and select the required columns.

Pro tip: Clarify the definition of 'successful' (e.g., status = 'success') and confirm that ties in amount should be broken by earlier timestamp, which ROW_NUMBER handles deterministically. Also mention that if ties should receive the same rank, use RANK or DENSE_RANK instead.

1. Understand the requirements

Identify the table schema, filter for successful transactions, and confirm the tie-breaking rule (amount descending, timestamp ascending).

2. Choose the ranking function

Use ROW_NUMBER() to assign a unique rank per customer based on the specified ordering, ensuring deterministic tie-breaking.

3. Write the SQL query

Construct a query with a window function partitioned by customer_id, ordered by amount DESC and transaction_ts ASC, then filter to ranks <= 3.

4. Select and order the output

Return customer_id, transaction_id, amount, transaction_ts, and the rank, ordering by customer_id and rank for readability.

5. Test and validate

Consider edge cases like customers with fewer than three successful transactions and verify that ties are handled correctly.

Key Points to Mention

  • Use of window functions (ROW_NUMBER, RANK, DENSE_RANK) and their differences.
  • Filtering for successful transactions (e.g., WHERE status = 'success').
  • Partitioning by customer_id and ordering by amount DESC, transaction_ts ASC.
  • Handling ties: ROW_NUMBER gives unique ranks; RANK/DENSE_RANK give same rank for ties.
  • Performance considerations: indexing on (customer_id, amount, transaction_ts) and avoiding full table scans.
  • Edge cases: customers with fewer than three transactions, null amounts, or duplicate timestamps.

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

Q3

Using a CTE and window functions, compute the average gap in days between consecutive successful transactions per customer over the past 180 days, then roll that up to the segment level. Return segment, number of customers covered, and average gap days.

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

This one got me.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

First, filter transactions to successful ones within the last 180 days and use a CTE with LAG to compute the gap in days between consecutive transactions per customer. Then, average those gaps per customer, and finally join to a segment dimension and average across customers to roll up to segment level, counting distinct customers.

Pro tip: Clarify the definition of 'gap'—whether it's between transaction dates or timestamps—and confirm that customers with only one transaction are excluded from the average gap calculation, as they have no consecutive pair.

1. Filter and prepare data

Select successful transactions from the past 180 days, ensuring you have customer_id, transaction_date, and segment. Use a CTE to isolate this subset.

2. Compute gaps with window functions

In a second CTE, use LAG(transaction_date) OVER (PARTITION BY customer_id ORDER BY transaction_date) to get the previous transaction date, then calculate the date difference in days.

3. Average gaps per customer

Aggregate the gaps per customer to get the average gap days for each customer, filtering out NULL gaps (first transaction).

4. Roll up to segment level

Join the per-customer averages to the segment mapping and compute the average gap days per segment, along with the count of distinct customers.

Key Points to Mention

  • Use of LAG window function to compute consecutive transaction gaps
  • Filtering for successful transactions and the 180-day window
  • Handling customers with only one transaction (exclude from average gap)
  • Date truncation or casting to ensure day-level differences
  • Joining to a segment dimension table or using a segment column
  • Counting distinct customers per segment for the roll-up

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