Average Sessions Per User
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
- L3
- Asked in
Problem
The board deck needs a single engagement benchmark: the average number of sessions per user across the entire platform.
How often do users come back?
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
| avg_sessions_per_user |
|---|
| 3.389830508474576 |
Worked solution and explanation
Why this problem exists in real interviews
The denominator is the whole question. user_sessions only contains users who actually had a session, so any average computed off this table is the engagement of active users, not the platform. If the board sees "4.2 sessions per user" and the inactive half of the user base is missing, that number is wrong for the deck it's going into.
Break down the requirements
Step 1: Count sessions per user
Inner query: COUNT(*) GROUP BY user_id. Every row in user_sessions is one session by construction (session_id is the grain), so COUNT(*) and COUNT(DISTINCT session_id) agree.
Step 2: Average the counts
Outer query averages those per-user counts. This is mathematically equivalent to COUNT(*) * 1.0 / COUNT(DISTINCT user_id) over the full table, and the one-pass form is dramatically cheaper at 200M rows.
Step 3: Confirm the denominator
If the deck wants average sessions across all registered users including zeros, you need a LEFT JOIN from users to user_sessions. The expected query treats inactive users as out of scope.
The solution
SELECT AVG(session_count) AS avg_sessions_per_user
FROM (
SELECT user_id, COUNT(*) AS session_count
FROM user_sessions
GROUP BY user_id
)COMMON FOLLOW-UP QUESTIONS
Common follow-up questions
- How would you compute the same metric only for the trailing 30 days? (Tests partition pruning via a `session_start >= CURRENT_DATE - INTERVAL '30 days'` filter on the partition key.)
- How would you include registered users with zero sessions in the average? (Probes `LEFT JOIN` from a `users` dimension and `COALESCE(session_count, 0)`.)
- How would you report median sessions per user instead of the mean? (Tests `PERCENTILE_CONT(0.5)` and an understanding of why median is the better summary for a long-tailed engagement distribution.)
- Subquery form (as written)
- Two scans of intermediate rows: inner
GROUP BY user_idbuilds a per-user count table, outerAVGreduces it. Readable, matches the spec exactly. - One-pass equivalent
SELECT COUNT(*) * 1.0 / COUNT(DISTINCT user_id) FROM user_sessions. Same answer, single aggregate, no intermediate materialization. At 200M rows the planner appreciates it.