cswork / libraryOpen practice workspace

Coding questions · Glean

Compute DAU and rolling MAU with zero days

This question evaluates a data scientist's proficiency in time-series analytics and data aggregation, focusing on computing daily active users and a 30-day rolling MAU from user and login tables, and falls under the Data Manipulation (SQL/Python) domain.

You have two tables in PostgreSQL:

Tables

users

  • user_id (STRING / INT, PK)
  • signup_date (DATE)

logins

  • user_id (STRING / INT, FK → users.user_id)
  • browser (TEXT)
  • login_ts (TIMESTAMP) — assume UTC unless stated otherwise

Tasks

  1. Daily Active Users (DAU): For a given date range (e.g., 2023-01-01 to 2023-12-31), compute DAU where DAU on a day = number of distinct user_id that logged in on that calendar day.

    • Output columns: date, dau
    • Requirement: Include all dates in the range, even if there were zero logins that day (show dau = 0).
    • Follow-up: If you need a date dimension/calendar, explain how you would create it in PostgreSQL (e.g., via generate_series).
  2. Rolling MAU by date: Compute a rolling-window MAU for each calendar date in the range.

    • Define MAU as distinct users who logged in in the last 30 days including the current date (an L30D lookback).
    • Output columns: date, mau_l30d
    • Follow-up discussion: If the product is primarily used on weekdays (little/no weekend usage), what are potential issues with an L30D definition, and what alternative window definitions could be more appropriate?

Compute DAU (include zero-activity days)

You are given two tables: users (who signed up) and logins (each login event). Write a SQL query (PostgreSQL) to compute daily active users (DAU) for each calendar date from 2025-05-25 to 2025-06-01 (inclusive).

Requirements:

  • DAU for a date = number of DISTINCT user_id values that logged in on that date.
  • If there are dates with no logins, they must still appear in the output with DAU = 0.
  • Output columns: activity_date, dau.
  • Order by activity_date ascending.

Table users

columntypeconstraints
user_idINTPRIMARY KEY
signup_dateDATENOT NULL

Sample data:

signup_dateuser_id
2025-04-011
2025-04-152
2025-05-013
2025-05-154
2025-05-305

Table logins

columntypeconstraints
login_idINTPRIMARY KEY
user_idINTNOT NULL
browserVARCHAR(20)NOT NULL
login_dateDATENOT NULL

Sample data:

browserlogin_datelogin_iduser_id
chrome2025-05-0512
safari2025-05-1223
chrome2025-05-1832
firefox2025-05-2141
chrome2025-05-225102
safari2025-05-236103
firefox2025-05-2571
chrome2025-05-258102
firefox2025-05-2791
safari2025-05-2810103
edge2025-05-30114
firefox2025-05-30121
chrome2025-06-01135

Expected output

One row per calendar date from 2025-05-25 to 2025-06-01 with DAU, including dates with no logins showing 0.

activity_datedau
2025-05-252
2025-05-260
2025-05-271
2025-05-281
2025-05-290
2025-05-302
2025-05-310
2025-06-011

Hints

  1. Use generate_series(start_date, end_date, interval '1 day') to create the missing dates.
  2. LEFT JOIN the generated calendar to the logins and aggregate with COUNT(DISTINCT user_id).

Compute rolling 30-day MAU by date (calendar-day window)

Using the same logins table, compute a rolling 30-day MAU for each calendar date from 2025-05-25 to 2025-06-01 (inclusive).

Definitions:

  • For each activity_date, MAU_30d = number of DISTINCT user_id values with at least one login in the calendar window from (activity_date - 29 days) through activity_date (inclusive).

Requirements:

  • Output must include all dates in the range even if there are no logins that day.
  • Output columns: activity_date, mau_30d.
  • Order by activity_date ascending.

Table users

columntypeconstraints
user_idINTPRIMARY KEY
signup_dateDATENOT NULL

Sample data:

signup_dateuser_id
2025-04-011
2025-04-152
2025-05-013
2025-05-154
2025-05-305

Table logins

columntypeconstraints
login_idINTPRIMARY KEY
user_idINTNOT NULL
browserVARCHAR(20)NOT NULL
login_dateDATENOT NULL

Sample data:

browserlogin_datelogin_iduser_id
chrome2025-05-0512
safari2025-05-1223
chrome2025-05-1832
firefox2025-05-2141
chrome2025-05-225102
safari2025-05-236103
firefox2025-05-2571
chrome2025-05-258102
firefox2025-05-2791
safari2025-05-2810103
edge2025-05-30114
firefox2025-05-30121
chrome2025-06-01135

Expected output

One row per calendar date from 2025-05-25 to 2025-06-01 with distinct users seen in the preceding 30 calendar days (inclusive).

activity_datemau_30d
2025-05-255
2025-05-265
2025-05-275
2025-05-285
2025-05-295
2025-05-306
2025-05-316
2025-06-017

Hints

  1. Generate the output dates first, then join logins using a BETWEEN condition for the rolling window.
  2. MAU is a distinct count over a date range, not a sum of DAU.

One-time user_id rehash: compute MAU overestimate (min/max %)

Sometimes a system performs a one-time full rehash of user IDs: the same real user gets a new user_id from a specific date onward.

You have:

  • logins: login events (after the rehash date, affected users appear under their new user_id).
  • user_id_rehash: mapping from old IDs to new IDs and the rehash_date when the new IDs start being used.

Task (PostgreSQL):

  1. For each calendar date from 2025-05-20 to 2025-06-01 (inclusive), compute rolling 30-day MAU in two ways:
    • naive_mau_30d: COUNT(DISTINCT logins.user_id) in [activity_date - 29, activity_date]
    • corrected_mau_30d: COUNT(DISTINCT canonical_user_id) in [activity_date - 29, activity_date], where canonical_user_id = old_user_id for rehashed users (i.e., map new IDs back to old IDs).
  2. For each date compute overestimate_pct = (naive_mau_30d - corrected_mau_30d) / corrected_mau_30d * 100.
  3. Return a single row with the maximum and minimum overestimate_pct observed across dates 2025-05-20 to 2025-06-01.

Output columns: max_overestimate_pct, min_overestimate_pct (rounded to 2 decimals).

Table users

columntypeconstraints
user_idINTPRIMARY KEY
signup_dateDATENOT NULL

Sample data:

signup_dateuser_id
2025-04-011
2025-04-152
2025-05-013
2025-05-154
2025-05-305

Table user_id_rehash

columntypeconstraints
old_user_idINTNOT NULL
new_user_idINTPRIMARY KEY
rehash_dateDATENOT NULL

Sample data:

new_user_idold_user_idrehash_date
10222025-05-20
10332025-05-20

Table logins

columntypeconstraints
login_idINTPRIMARY KEY
user_idINTNOT NULL
browserVARCHAR(20)NOT NULL
login_dateDATENOT NULL

Sample data:

browserlogin_datelogin_iduser_id
chrome2025-05-0512
safari2025-05-1223
chrome2025-05-1832
firefox2025-05-2141
chrome2025-05-225102
safari2025-05-236103
firefox2025-05-2571
chrome2025-05-258102
firefox2025-05-2791
safari2025-05-2810103
edge2025-05-30114
firefox2025-05-30121
chrome2025-06-01135

Expected output

A single-row summary showing the maximum and minimum overestimation percentages caused by counting rehashed IDs as different users (within the date range 2025-05-20 to 2025-06-01).

max_overestimate_pctmin_overestimate_pct
66.670

Hints

  1. Map new_user_id back to old_user_id to get a canonical ID before doing DISTINCT counting.
  2. Compute naive vs corrected per day first, then take MAX/MIN over the date range.