The Ninety-Day Comeback

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
TravelClick

Problem

A user is considered 'retained' if they start at least one additional session strictly after their very first session, but no later than 90 calendar days after that first session. Working from the user_sessions table, treat each user's earliest session_start (by calendar date) as their first session. Calculate the proportion of retained users out of all users who have any session. Return a single retention rate as a REAL value (retained users divided by total users).

Everyone shows up once. Who comes back before the quarter ends?

Tables

user_sessions

Columns: session_id INTEGER, user_id INTEGER, device_id INTEGER, session_start TEXT, session_duration_sec INTEGER, pages_viewed INTEGER

user_sessions: 10 of 200 rows
session_iduser_iddevice_idsession_startsession_duration_secpages_viewed
70611972302026-02-02 01:00:00533
71222942592026-03-03 02:00:00766
71833912882026-04-04 03:00:00999
72444883172026-05-05 04:00:0012212
73055853462026-06-06 05:00:0014515
73666823752026-07-07 06:00:0016818
74277794042026-08-08 07:00:0019121
7488876NULL2026-09-09 08:00:0021424
75499734622026-01-10 09:00:0023727
761010704912026-02-11 10:00:0026030

Expected output

First 1 of 1 rows
retention_rate
0.576271186440678

Worked solution and explanation

What this really measures

Strip the product framing and this is first-touch anchored, bounded-window retention: anchor every user to their first calendar day, then ask what share of them come back strictly later but inside 90 days. A plain GROUP BY on user_id cannot say that; you have to re-join each user's first date to their own sessions on an inequality window. The trap is subtle. Compare raw timestamps and a second session later on the same first day leaks in as a return; reach for an INNER JOIN and every user who never came back vanishes from the denominator, pinning the rate at 100%; divide two integer counts and it collapses to 0 or 1. Miss any one and the number you report is meaningless.


Break down the requirements

Step 1: Anchor each user's first day

GROUP BY user_id with MIN(DATE(session_start)) collapses each user to a single first calendar date. Using DATE() here, not the raw timestamp, is what stops a later session on that same first day from counting as a comeback.

Step 2: Flag the users who came back in time

Join the first-day anchor back to user_sessions where DATE(us.session_start) > fs.first_date and <= DATE(fs.first_date, '+90 day'), then GROUP BY user_id to get the distinct set of retained users. The strict > excludes the first day; the inclusive <= keeps day 90.

Step 3: Divide distinct retained by distinct total

LEFT JOIN the retained set onto the full session table and compute CAST(COUNT(DISTINCT ru.user_id) AS REAL) / COUNT(DISTINCT us.user_id). Counting over every session row is exactly why the DISTINCT matters: a user with eight sessions still counts once on each side.


The solution

First-day anchor, 90-day return check, distinct-user ratio
WITH first_sessions AS (
    SELECT user_id, MIN(DATE(session_start)) AS first_date
    FROM user_sessions
    GROUP BY user_id
),
retained_users AS (
    SELECT fs.user_id
    FROM first_sessions fs
    JOIN user_sessions us
        ON fs.user_id = us.user_id
        AND DATE(us.session_start) > fs.first_date
        AND DATE(us.session_start) <= DATE(fs.first_date, '+90 day')
    GROUP BY fs.user_id
)
SELECT CAST(COUNT(DISTINCT ru.user_id) AS REAL) / COUNT(DISTINCT us.user_id) AS retention_rate
FROM user_sessions us
LEFT JOIN retained_users ru ON us.user_id = ru.user_id

Common follow-up questions