Most Popular Signup Day
A medium SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.
- Domain
- SQL
- Difficulty
- medium
- Seniority
- L4
- Asked in
- Airbnb
Problem
For each weekday, give us how many users signed up on it and the fraction of those signups that reported an age bracket, as a decimal between 0 and 1. Our promotions only run Monday through Friday, so weekend signups stay out of it.
One day of the week wins on signups.
Tables
users
Columns: user_id INTEGER, username TEXT, email TEXT, signup_date TEXT, account_status TEXT, age_bucket TEXT
| user_id | username | signup_date | account_status | age_bucket | |
|---|---|---|---|---|---|
| 100 | alice | alice@example.com | 2025-02-02 | inactive | 25-34 |
| 197 | aaron42 | aaron42@example.com | 2026-03-03 | suspended | 35-44 |
| 294 | amelia | amelia@example.com | 2024-04-04 | pending_verification | 45-54 |
| 391 | arjun | arjun@example.com | 2025-05-05 | active | 55-64 |
| 488 | ava99 | ava99@example.com | 2026-06-06 | inactive | 65+ |
| 585 | andrew_k | andrew_k@example.com | 2024-07-07 | suspended | NULL |
| 682 | anika | anika@example.com | 2025-08-08 16:34:00 | pending_verification | 18-24 |
| 779 | aiden | aiden@example.com | 2026-09-09 14:53:00 | active | 25-34 |
| 876 | aria_b | aria_b@example.com | 2024-10-10 | inactive | 35-44 |
| 973 | brian | brian@example.com | 2025-11-11 | suspended | 45-54 |
Expected output
| signup_day | signup_count | age_reported_rate |
|---|---|---|
| Monday | 31 | 0.8709677419354839 |
| Tuesday | 28 | 0.9642857142857143 |
| Wednesday | 26 | 0.8846153846153846 |
| Thursday | 30 | 0.9 |
| Friday | 28 | 0.8214285714285714 |
Worked solution and explanation
What this is really asking
Strip the costume and this is a data-completeness rate per bucket over a business-day-only population: label each signup with its weekday, drop the weekend, then divide the signups that filled in age_bucket by all signups in the bucket. Everyone writes the CASE and the GROUP BY. What separates candidates is the division. Both counts are integers, and integer divided by integer truncates, so a day where 26 of 30 signups reported a bracket comes back as 0. Get that wrong and every weekday reads 0, which says nobody ever tells you their age and sends someone chasing a broken signup form that is working fine.
Building it step by step
Step 1: Turn the date into a weekday name
STRFTIME('%w', signup_date) returns the weekday as a text digit, zero-indexed at Sunday. A simple CASE on that digit maps '1' to 'Monday' through '5' to 'Friday'; compare against text literals, because text is what the function gives you.
Step 2: Keep only the business days
Sunday is '0' and Saturday is '6'. The WHERE collapses those two to NULL with a CASE and keeps rows that are IS NOT NULL, so only business days reach the aggregate. Skip it and you get two extra buckets with a NULL day name.
Step 3: Let the column count do the filtering
A skipped age question is stored as NULL, and COUNT(age_bucket) skips NULL on its own, so it counts exactly the signups that reported a bracket. COUNT(*) counts every row and is the day's total. No condition needed: the column count already is the filter.
Step 4: Divide as real numbers
Group by the weekday digit, not the label. Wrap COUNT(age_bucket) in CAST(... AS REAL) before dividing by COUNT(*), and the ratio survives as a fraction between 0 and 1.
SELECT
CASE STRFTIME('%w', signup_date)
WHEN '1' THEN 'Monday'
WHEN '2' THEN 'Tuesday'
WHEN '3' THEN 'Wednesday'
WHEN '4' THEN 'Thursday'
WHEN '5' THEN 'Friday'
END AS signup_day,
COUNT(*) AS signup_count,
CAST(COUNT(age_bucket) AS REAL) / COUNT(*) AS age_reported_rate
FROM users
WHERE (CASE WHEN STRFTIME('%w', signup_date) IN ('0', '6') THEN NULL ELSE 1 END) IS NOT NULL
GROUP BY STRFTIME('%w', signup_date)The WHERE nulls the weekend and keeps business days; the CAST on COUNT(age_bucket) turns integer division into a real fraction.
- Integer division
COUNT(age_bucket) / COUNT(*)gives 26 / 30 = 0. Every weekday reads 0, and nothing errors.- Real division
CAST(COUNT(age_bucket) AS REAL) / COUNT(*)gives 26.0 / 30 = 0.867. One cast on either operand is enough.
Common follow-up questions
- Growth wants the share shown as a percentage with one decimal. Where does the rounding go, and why not before the division? (Tests presentation-layer precision without losing the real division.)
- What changes if some clients write an empty string into `age_bucket` instead of `NULL` when the question is skipped? (Tests the difference between NULL and an empty string.)
- If a weekday had no signups at all, it would be missing from this output. How would you guarantee all five business days appear? (Tests awareness of empty buckets.)