Skip to content
Reliable Data Engineering
Practice problem medium cohortsretentionwindow-functions
Solve it in the browser (SQL editor)

Cohort Retention Matrix

Difficulty: Medium · Topics: cohorts, retention, window-functions · Asked at: Airbnb, Uber, Robinhood, Duolingo

Problem

A user’s cohort is the month of their first activity. For each cohort and month_n (0 = cohort month, 1 = next month, …) return cohort, month_n, active_users, cohort_size, retention_pct (rounded to 1 decimal). Order by cohort, month_n.

Schema and sample data

CREATE TABLE activity (user_id INTEGER, activity_date TEXT);
INSERT INTO activity VALUES
(1,'2026-01-05'),(1,'2026-02-07'),(1,'2026-03-01'),
(2,'2026-01-20'),(2,'2026-03-15'),
(3,'2026-01-25'),
(4,'2026-02-03'),(4,'2026-02-20'),(4,'2026-03-08'),
(5,'2026-02-14'),
(6,'2026-03-30');

Expected output

cohortmonth_nactive_userscohort_sizeretention_pct
2026-01033100.0
2026-0111333.3
2026-0122366.7
2026-02022100.0
2026-0211250.0
2026-03011100.0

Hints

Hint 1

Cohort = MIN(month) per user.

Hint 2

Month difference = (year12 + month) − (cohort_year12 + cohort_month).

Solution

WITH um AS (
  SELECT DISTINCT user_id,
         CAST(strftime('%Y', activity_date) AS INTEGER) * 12 + CAST(strftime('%m', activity_date) AS INTEGER) AS mi
  FROM activity
), cohorts AS (
  SELECT user_id, MIN(mi) AS cohort_mi FROM um GROUP BY user_id
), grid AS (
  SELECT c.cohort_mi, um.mi - c.cohort_mi AS month_n, COUNT(*) AS active_users
  FROM um JOIN cohorts c USING (user_id)
  GROUP BY c.cohort_mi, um.mi - c.cohort_mi
)
SELECT printf('%04d-%02d', (cohort_mi - 1) / 12, (cohort_mi - 1) % 12 + 1) AS cohort,
       month_n, active_users,
       MAX(CASE WHEN month_n = 0 THEN active_users END) OVER (PARTITION BY cohort_mi) AS cohort_size,
       ROUND(100.0 * active_users / MAX(CASE WHEN month_n = 0 THEN active_users END) OVER (PARTITION BY cohort_mi), 1) AS retention_pct
FROM grid
ORDER BY cohort, month_n;

Explanation

Follow-up questions

Why can the latest cohorts look like they have better retention?

Survivorship and incomplete periods: recent cohorts have fewer observable months, and partial months inflate or deflate rates. Compare cohorts only on months that are complete for all of them.