Retention Analysis (Cohorts)

⭐ Interview Importance: HIGH
⏱️ Revision Time: 4 min

Concept

“How many users who signed up in January came back and logged in during February?”
This is Cohort Retention Analysis, the single most important metric for any SaaS or consumer startup. If retention is 0%, your business is dead, regardless of how many people sign up.

To calculate retention in SQL, we must track users across multiple time periods.
We achieve this by assigning every user to a “Cohort” (their signup month), finding all their subsequent login months, and calculating the mathematical difference in months between the two.

Step 1: Assigning the Cohort

First, we need to know when every user signed up. We use a CTE to find their absolute first login date and truncate it to the month.

WITH UserCohorts AS (
    SELECT 
        user_id,
        -- Truncate their very first login to the 1st of the month
        DATE_TRUNC('month', MIN(login_date)) AS cohort_month
    FROM logins
    GROUP BY user_id
)

Step 2: Calculate the “Month Offset”

Next, we JOIN the original logins table back to the Cohort table.
For every login a user ever made, we calculate how many months passed between their cohort_month and that specific login_month.

, UserActivity AS (
    SELECT 
        c.user_id,
        c.cohort_month,
        DATE_TRUNC('month', l.login_date) AS activity_month,
        
        -- Calculate the difference in months (PostgreSQL specific math)
        EXTRACT(year FROM l.login_date) * 12 + EXTRACT(month FROM l.login_date) 
        - (EXTRACT(year FROM c.cohort_month) * 12 + EXTRACT(month FROM c.cohort_month)) AS month_offset
        
    FROM UserCohorts c
    INNER JOIN logins l ON c.user_id = l.user_id
)

If a user signed up in Jan, and logged in during Jan, the month_offset is 0.
If they logged in during Feb, the month_offset is 1.

Step 3: Count and Pivot the Matrix

Finally, we just count how many distinct users exist for every single combination of cohort_month and month_offset.

SELECT 
    cohort_month,
    -- Month 0 (The original signup month, always 100% of the cohort)
    COUNT(DISTINCT CASE WHEN month_offset = 0 THEN user_id END) AS month_0,
    -- Month 1 Retention
    COUNT(DISTINCT CASE WHEN month_offset = 1 THEN user_id END) AS month_1,
    -- Month 2 Retention
    COUNT(DISTINCT CASE WHEN month_offset = 2 THEN user_id END) AS month_2
FROM UserActivity
GROUP BY cohort_month
ORDER BY cohort_month ASC;

The Output Matrix:

cohort_monthmonth_0month_1month_2
2023-01-0150001000500
2023-02-014000800NULL

(Note: February doesn’t have Month 2 data yet, because we are currently in March).

To get the actual percentage (e.g., 20% retention in Month 1), you simply wrap the COUNT in math: (month_1 / month_0) * 100.

Interview Questions

Q: In Step 3, why did we explicitly use COUNT(DISTINCT user_id) instead of just COUNT(*)?
A: If we used COUNT(*), we would be counting the total number of login events. If a highly active user logged in 50 times during February, COUNT(*) would add 50 to the month_1 bucket. Our retention percentage would skyrocket to 1500%, mathematically corrupting the entire analysis.
Retention analysis requires knowing if the user came back at least once. COUNT(DISTINCT user_id) mathematically guarantees that the active user is only counted exactly 1 time for February, keeping the retention metrics accurate.