Last Seen
An easy SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.
- Domain
- SQL
- Difficulty
- easy
- Seniority
- L3
- Asked in
- Moore Capital Management
Problem
Our recency model keeps scoring users on stale activity because it reads every row of user_sessions rather than one latest start per user. Give each user's most recent session start, from the lowest user ID up to the highest.
Everyone has a most recent session.
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 | latest_session_start |
|---|---|
| 100 | 2026-09-01 08:00:00 |
| 197 | 2026-08-06 13:00:00 |
| 294 | 2026-09-07 14:00:00 |
| 391 | 2026-08-16 19:00:00 |
| 488 | 2026-09-05 20:00:00 |
Worked solution and explanation
What this really is
Under the retention costume this is a grain collapse. user_sessions holds one row per session, the model wants one row per user, and the only thing that survives the collapse is the freshest session_start. Anyone can find a latest timestamp. What separates candidates is whether they fold the group or just trim it. SELECT DISTINCT user_id, session_start looks tidy, but it still returns one row per session. A user with eight sessions shows up eight times, and the recency model joins on eight 'latest' dates. The opposite slip, MAX with no grouping, returns one global date for the whole table.
Walking through it
Step 1: Fold every session into its user
GROUP BY user_id is what makes the output one row per user. Without it the aggregate spans all of user_sessions. With DISTINCT instead, you only remove exact repeated pairs, so every session that started at a different moment survives.
Step 2: Take the latest start inside each group
MAX(session_start) returns each user's freshest start. session_start is TEXT, but it is stored as zero-padded 'YYYY-MM-DD HH:MM:SS', so string order is time order and no parsing is needed.
Step 3: List users from the lowest ID up
The roster is read lowest user_id first, and that order is part of the answer, so ORDER BY user_id stays. The grouping leaves exactly one row per user_id, so the sort has no ties to break.
The solution
SELECT user_id, MAX(session_start) AS latest_session_start
FROM user_sessions
GROUP BY user_id
ORDER BY user_id- Trims the rows
SELECT DISTINCT user_id, session_startremoves only repeated pairs. Every session with its own start survives, so the result keeps the session grain.- Folds the group
GROUP BY user_idwithMAX(session_start)turns each user's sessions into one row, so the result has exactly one row per user: 59 on this data.
Common follow-up questions
- Now return the `session_id` of that latest session too, not just its start time. How does the query change? (Aggregation returns the timestamp but not the session that owns it; this forces a window function or a join back on (`user_id`, `session_start`).)
- If a user has two sessions that start at the identical timestamp, which one wins, and does your answer still return a single row? (Tests tie handling: two sessions sharing the exact maximum timestamp for one user.)
- At this scale, what index would you build so this stays cheap as the table grows? (Tests indexing intuition on a high-cardinality grouping key at 50M rows.)