Low-Engagement Sessions
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
- L3
Problem
Which users average less than 1,000 seconds per session across everything they have in user_sessions, and what is each one's average, for the retention team's weak-engagement list?
Users whose average session duration is below the engagement threshold
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 | avg_duration |
|---|---|
| 197 | 973 |
| 294 | 996 |
| 488 | 888.6666666666666 |
| 2331 | 600 |
| 2719 | 600 |
Worked solution and explanation
This is a filter on a per-user summary dressed up as a retention question. The 1,000-second line belongs to each user's average across all their rows in user_sessions, not to any single session, so the test can only run after sessions collapse to one row per user_id. Anyone can write AVG(session_duration_sec). What separates candidates is where the < 1000 goes: put it in WHERE and it trims long sessions before any average exists, so most users come back with the average of their short sessions instead of the 12 users whose whole history averages low.
Getting to the answer
Step 1: Settle the grain first
The output is one row per user, so user_id is the only key. Users here have between one and eight sessions, and the average has to see every one of them before anyone is judged.
Step 2: Average each user's sessions once
AVG(session_duration_sec) per user_id. Seventeen sessions have no recorded duration; AVG skips them in both the sum and the count, so the average reflects only sessions that were actually timed.
Step 3: Test the threshold after the average exists
HAVING AVG(session_duration_sec) < 1000 runs on the grouped rows, the only place the per-user average is visible. WHERE sees one session at a time and has no average to compare.
SELECT user_id, AVG(session_duration_sec) AS avg_duration
FROM user_sessions
GROUP BY user_id
HAVING AVG(session_duration_sec) < 1000- Filter sessions, then average
WHERE session_duration_sec < 1000drops every long session first. A user with sessions of 400 and 3,000 seconds really averages 1,700, but shows up here at 400.- Average, then filter users
HAVING AVG(session_duration_sec) < 1000keeps both sessions in the average, sees 1,700, and leaves that user out.
SELECT user_id, AVG(session_duration_sec) AS avg_duration
FROM user_sessions
WHERE session_duration_sec < 1000
GROUP BY user_idWrong: it averages only the short sessions, so a long session can never disqualify anyone.
Common follow-up questions
- How would you also require at least three sessions before judging a user? (Tests adding a second aggregate condition, `COUNT(*) >= 3`, to the same group filter.)
- What happens to a user whose every `session_duration_sec` is `NULL`? (Tests that the average is `NULL`, the comparison is unknown, and the user drops out.)
- If `user_sessions` grows to billions of rows, how would you keep a daily version of this cheap? (Tests incremental rollups: store each user's sum and count, never the average itself.)