Users Who Churned in February
A hard SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.
- Domain
- SQL
- Difficulty
- hard
- Seniority
- L4
- Asked in
- ESPN
Problem
Which users logged at least one session in user_sessions between November 2025 and the end of January 2026, then went all of February 2026 without one? The retention analyst asking wants each lapsed user listed once.
Gone in February.
Tables
user_sessions
Columns: session_id INTEGER, user_id INTEGER, device_id INTEGER, session_start TEXT, session_duration_sec INTEGER, pages_viewed INTEGER
| session_id | user_id | device_id | session_start | session_duration_sec | pages_viewed |
|---|---|---|---|---|---|
| 7061 | 197 | 230 | 2026-02-02 01:00:00 | 53 | 3 |
| 7122 | 294 | 259 | 2026-03-03 02:00:00 | 76 | 6 |
| 7183 | 391 | 288 | 2026-04-04 03:00:00 | 99 | 9 |
| 7244 | 488 | 317 | 2026-05-05 04:00:00 | 122 | 12 |
| 7305 | 585 | 346 | 2026-06-06 05:00:00 | 145 | 15 |
| 7366 | 682 | 375 | 2026-07-07 06:00:00 | 168 | 18 |
| 7427 | 779 | 404 | 2026-08-08 07:00:00 | 191 | 21 |
| 7488 | 876 | NULL | 2026-09-09 08:00:00 | 214 | 24 |
| 7549 | 973 | 462 | 2026-01-10 09:00:00 | 237 | 27 |
| 7610 | 1070 | 491 | 2026-02-11 10:00:00 | 260 | 30 |
Expected output
| user_id |
|---|
| 973 |
| 1846 |
| 779 |
| 1652 |
| 585 |
Worked solution and explanation
What this really is
This is a per-user set difference dressed up as a retention question: everyone seen over the winter, minus everyone seen in February. Anyone can write the exclusion. What separates candidates is remembering that the left side is a session log spanning three months, not a list of users. A user with four winter sessions is four rows until you say otherwise, and if you forget, your lapsed list names your most engaged lapsed users several times each.
Building it
Step 1: Bound the winter window with half-open dates
session_start >= '2025-11-01' AND session_start < '2026-02-01' covers November through January, crosses the year boundary without any month arithmetic, and catches a session at 23:59 on January 31.
Step 2: Exclude the February set
The subquery reads user_sessions a second time for February and returns its user_id values; NOT IN drops any winter row whose user shows up there. The subquery needs no DISTINCT: membership does not care about repeats.
Step 3: Collapse to one row per user
SELECT DISTINCT user_id is the step that turns surviving sessions into surviving users. It belongs on the outer query, where the repeats actually reach the output.
SELECT DISTINCT user_id
FROM user_sessions
WHERE session_start >= '2025-11-01'
AND session_start < '2026-02-01'
AND user_id NOT IN (
SELECT user_id
FROM user_sessions
WHERE session_start >= '2026-02-01'
AND session_start < '2026-03-01'
)- Without the dedup
SELECT user_idreturns one row per qualifying winter session, so a user with three sessions in December appears three times.- With it
SELECT DISTINCT user_idreturns each lapsed user exactly once, which is what a list of who left actually means.
Common follow-up questions
- How would you turn this into a churn rate for the winter cohort? (Count the lapsed set and divide by the count of unique winter users.)
- How would you rewrite the exclusion with `EXCEPT`? (`EXCEPT` dedups both sides on its own and has no NULL trap.)
- What if you needed this for every month, not just February? (Generalize to monthly active sets and compare each month to its trailing window.)
- What changes if a user who skipped February came back in March? (Tests whether 'lapsed' means a gap or a permanent exit.)