PySpark Coding
Interview Questions
Hands-on PySpark problems: joins, window functions, aggregations, UDFs, and performance tuning scenarios.
Scenario: for each customer, show the month-over-month change and a running total.
MediumGiven monthly spend per customer, add the previous month's amount, the percentage change and the cumulative spend so far.
Expected output
Points interviewers look for
- The first month has no previous month, so
lagreturns NULL and so does the change: do not turn it into 0%. - An explicit
rowsBetween(unboundedPreceding, currentRow)frame. With onlyorderBy, the default frame is RANGE-based, so rows with the same month would all get the same running total. - A missing month (no row for February) makes
lagcompare March with January; fill the calendar first if that matters.
Practise this: Month-over-month growth percentage (runs in the browser, with test cases).
Scenario: find the top 2 salaries in each department, keeping employees who tie.
MediumReturn every employee whose salary is among the two highest salary levels of their department. Two people with the same salary must both appear.
Expected output
Choosing the ranking function
| Function | IT ranks for 90k, 85k, 85k, 70k | Use when |
|---|---|---|
row_number | 1, 2, 3, 4 | Exactly N rows per group; ties broken arbitrarily |
rank | 1, 2, 2, 4 | Ties share a rank and the next rank is skipped |
dense_rank | 1, 2, 2, 3 | Top N distinct values; ties kept, no gaps |
Sales has one employee, so it returns one row: “top 2” never invents rows.
Practise this: Top 3 highest-paid employees in each department (runs in the browser, with test cases).
Scenario: a table receives several updates per customer. How do you keep only the latest record per key?
MediumA CDC feed or a daily extract often carries more than one version of the same customer. Keep the newest version of each key, deterministically, even when two updates share a timestamp.
Expected output
What the interviewer is checking
row_number(), notrank(): rank would keep both tied rows for customer 103.- A deterministic tie-breaker (an ingestion sequence or offset). Without it, which tied row survives can change between runs.
- Why not
dropDuplicates(["customer_id"]): it keeps an arbitrary row, not the latest. - On Delta Lake, the same window result feeds a
MERGEso the target table holds one current row per key.
Practise this: Deduplicate: keep the latest record per customer (runs in the browser, with test cases).
You receive employee data in Spark. How would you calculate a 15% bonus column?
Easy
Expected output
Practise this: Add a bonus column with withColumn (runs in the browser, with test cases).
Find employees whose salary is greater than 55,000
Easy
Expected output
Priya earns exactly 55,000, so > 55000 excludes her; use >= to include it.
Practise this: Filter rows with a condition (runs in the browser, with test cases).
A bank wants to identify customers whose salary has significantly declined over the last 3 months.
HardBusiness Requirement
A bank wants to identify customers whose salary has significantly declined over the last 3 months.
Risk Criteria
- Customer should have monthly salary credits.
- Compare the latest month's salary with the average salary of the previous 3 months.
- If the salary has dropped by more than 40%, flag the customer.
- Ignore customers having fewer than 4 salary credits.
Input DataFrame
account_transactions
| account_id | txn_date | txn_type | amount |
|---|---|---|---|
| 1001 | 2024-01-05 | CREDIT | 100000 |
| 1001 | 2024-02-05 | CREDIT | 105000 |
| 1001 | 2024-03-05 | CREDIT | 98000 |
| 1001 | 2024-04-05 | CREDIT | 50000 |
| 1002 | 2024-01-05 | CREDIT | 70000 |
| 1002 | 2024-02-05 | CREDIT | 72000 |
| 1002 | 2024-03-05 | CREDIT | 71000 |
| 1002 | 2024-04-05 | CREDIT | 70000 |
Expected Output
| account_id | avg_previous_3_months | current_salary | drop_percentage | risk_flag |
|---|---|---|---|---|
| 1001 | 101000.00 | 50000 | 50.50% | FLAG |
| 1002 | 71000.00 | 70000 | 1.41% | NOT_FLAG |
Practise this: Detect significant salary declines (runs in the browser, with test cases).
Given a DataFrame containing flight/train routes and fares, find the cheapest fare between a source and destination, including the possibility of travelling through an intermediate city?
MediumInput DataFrame
Suppose we have this route data:
| source | destination | fare |
|---|---|---|
| Delhi | Mumbai | 5000 |
| Delhi | Pune | 3000 |
| Pune | Mumbai | 1500 |
| Delhi | Bangalore | 4000 |
| Bangalore | Mumbai | 2500 |
| Delhi | Chennai | 4500 |
| Chennai | Mumbai | 3000 |
We want to find the cheapest way to travel:
Delhi → Mumbai
Possible routes
| Route | Calculation | Total Fare |
|---|---|---|
| Delhi → Mumbai | 5000 | 5000 |
| Delhi → Pune → Mumbai | 3000 + 1500 | 4500 |
| Delhi → Bangalore → Mumbai | 4000 + 2500 | 6500 |
| Delhi → Chennai → Mumbai | 4500 + 3000 | 7500 |
Therefore:
Cheapest fare = ₹4,500
Route = Delhi → Pune → Mumbai
Expected output
Why include the direct route?
The question asks for the cheapest fare including one stop, so a direct flight must compete too. With only the self join, a cheaper direct fare is never considered: change Delhi → Mumbai to 4000 and the one-stop-only version still answers 4500, while this one answers 4000 (Delhi -> Mumbai).
How to explain it: “I build the candidates two ways: direct routes by filtering, and one-stop routes by self-joining the table on destination = source. I union them, sort by total fare and take the first row. For more than one stop this becomes a graph problem (shortest path), not a fixed number of joins.”
Remember the logic
Direct routes ∪ (self join → add fares) → sort ascending → limit(1)
Given a sales DataFrame containing product_id, unit_price, and quantity, write PySpark code to calculate the total amount for each product and the overall total sales amount.
Easy1. Input DataFrame
Think of the DataFrame like this table:
| product_id | unit_price | quantity |
|---|---|---|
| 101 | 100.0 | 2 |
| 102 | 200.0 | 3 |
| 103 | 50.0 | 4 |
| 104 | 150.0 | 2 |
For each row:
Total Amount = unit_price × quantity
Expected output
Find the number of transactions that occurred for each product. Output the product name along with the transaction count and sort by product_id
HardYou are given two datasets:
1. Inventory Table
product_id
product_name
2. Transactions Table
transaction_id
time
product_id
Output the product name along with the transaction count and sort by product_id.
Handle duplicate transactions (keep latest)
Count the number of transactions per product
Exclude products with no transactions
Sort the result by product_id
Expected output
Transaction 118 appears twice for product 5; keeping only its latest row leaves 2 transactions, not 3. Banana (no transactions) and product 7 (not in the inventory) drop out of the inner join.
Find customers who have placed at least one order (PySpark join)
EasyWrite a PySpark program to perform an JOIN and return only customers who have placed at least one order.
Better: a left semi join
The inner join returns a customer once per order. Give C001 a second order and C001 appears twice. A left_semi join keeps each matching customer once and returns only the customer columns:
Expected output
The opposite question, customers with no orders, is how="left_anti".
Practise this: Customers with at least one order (runs in the browser, with test cases).