The First Year
A medium SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.
- Domain
- SQL
- Difficulty
- medium
- Seniority
- L4
- Asked in
- Dropbox
Problem
We want the early adopters who came back, not the ones who signed up and vanished: the users who logged at least two sessions within a year of joining. For each of them, surface the user ID, signup date, and how many sessions landed inside that first-year window, smallest user ID first.
Activated early. A good sign.
Tables
users
Columns: user_id INTEGER, username TEXT, email TEXT, signup_date TEXT, account_status TEXT, age_bucket TEXT
| user_id | username | signup_date | account_status | age_bucket | |
|---|---|---|---|---|---|
| 100 | alice | alice@example.com | 2025-02-02 | inactive | 25-34 |
| 197 | aaron42 | aaron42@example.com | 2026-03-03 | suspended | 35-44 |
| 294 | amelia | amelia@example.com | 2024-04-04 | pending_verification | 45-54 |
| 391 | arjun | arjun@example.com | 2025-05-05 | active | 55-64 |
| 488 | ava99 | ava99@example.com | 2026-06-06 | inactive | 65+ |
| 585 | andrew_k | andrew_k@example.com | 2024-07-07 | suspended | NULL |
| 682 | anika | anika@example.com | 2025-08-08 16:34:00 | pending_verification | 18-24 |
| 779 | aiden | aiden@example.com | 2026-09-09 14:53:00 | active | 25-34 |
| 876 | aria_b | aria_b@example.com | 2024-10-10 | inactive | 35-44 |
| 973 | brian | brian@example.com | 2025-11-11 | suspended | 45-54 |
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 | signup_date | session_count |
|---|---|---|
| 197 | 2026-03-03 | 3 |
| 391 | 2025-05-05 | 3 |
| 488 | 2026-06-06 | 2 |
| 682 | 2025-08-08 16:34:00 | 4 |
| 973 | 2025-11-11 | 5 |
Worked solution and explanation
What this really is
This looks like a plain users-to-sessions join, but the real skill is two filters stacked on a per-user computed interval. Each user's window opens at their own signup and runs 365 days, and then you keep only the users who came back: at least two sessions inside that window. A plain grouped count hands back every user who logged even one qualifying session, which is exactly the one-and-done crowd you are trying to exclude. The threshold on the aggregate is what separates a returning user from a one-time visitor, and it can only be applied after the grouping. Two traps sit underneath: sessions dated before signup produce a negative day difference and sneak in under an upper-bound-only filter, and the INNER JOIN quietly drops session-less accounts, which is what you want. Miss the aggregate threshold and you fill your sticky-early-adopter cohort with people who visited once and never returned.
Break down the requirements
Step 1: Join users to sessions
INNER JOIN users to user_sessions on user_id so only users with sessions are considered. Accounts with no sessions at all fall out here for free.
Step 2: Filter sessions within 365 days of signup
Filter to sessions whose session_start falls between the signup date and 365 days later using julianday() date arithmetic. The lower bound of 0 is what excludes sessions logged before signup.
Step 3: Aggregate per user
GROUP BY user_id and signup_date and COUNT(*) the qualifying sessions per user.
Step 4: Keep only users who came back
Apply HAVING COUNT(*) >= 2 to keep only users who returned. This runs after the grouping, which is why it belongs in HAVING and not WHERE: the per-user count does not exist until aggregation happens.
The solution
SELECT u.user_id, u.signup_date, COUNT(*) AS session_count
FROM users u
INNER JOIN user_sessions us ON u.user_id = us.user_id
WHERE julianday(us.session_start) - julianday(u.signup_date) BETWEEN 0 AND 365
GROUP BY u.user_id, u.signup_date
HAVING COUNT(*) >= 2
ORDER BY u.user_idCommon follow-up questions
- How would you also list one-time visitors, flagged separately from returning users? (Tests LEFT JOIN with COALESCE plus keeping the HAVING logic off the join side.)
- How would you compute the return rate as a percentage of all signups? (Tests window functions or a separate total count in a CTE.)
- What if session timestamps had timezone offsets? (Tests timezone normalization in date arithmetic.)