The Engagement Gap

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
Microsoft

Problem

We're chasing the engagement gap: among active, inactive, and suspended accounts only (leave out anyone still awaiting verification), find the days when our two lapsed segments, the inactive and the suspended, together drew more than half of that day's page views. For each such day, report how many views came from the inactive users and how many from the suspended, earliest first.

Two kinds of gone. One still stirs more.

Tables

page_views

Columns: view_id INTEGER, page_url TEXT, user_id INTEGER, referrer TEXT, dur_ms INTEGER, device TEXT, viewed_at TEXT

page_views: 10 of 200 rows
view_idpage_urluser_idreferrerdur_msdeviceviewed_at
5053/products1555amazon.com637desktop2026-09-02 01:09:00
5106/checkout1652twitter.com774tablet2026-09-03 02:18:00
5159/profile1749github.com911Mobile2026-09-04 03:27:00
5212/search1846NULL1048DESKTOP2026-09-05 04:36:00
5265/about1943reddit.com1185mobile2026-09-06 05:45:00
5318/blog/intro1555NULLNULLdesktop2026-09-07 06:54:00
5371/pricing1652bing.com1459tablet2026-09-08 07:03:00
5424/docs/api1749news.ycombinator.com1596Mobile2026-09-09 08:12:00
5477/settings1846NULL1733DESKTOP2026-09-10 09:21:00
5530/Home1943bing.com1870mobile2026-09-11 10:30:00

users

Columns: user_id INTEGER, username TEXT, email TEXT, signup_date TEXT, account_status TEXT, age_bucket TEXT

users: 10 of 200 rows
user_idusernameemailsignup_dateaccount_statusage_bucket
100alicealice@example.com2025-02-02inactive25-34
197aaron42aaron42@example.com2026-03-03suspended35-44
294 ameliaamelia@example.com2024-04-04pending_verification45-54
391arjun arjun@example.com2025-05-05active55-64
488ava99ava99@example.com2026-06-06inactive65+
585andrew_kandrew_k@example.com2024-07-07suspendedNULL
682anikaanika@example.com2025-08-08 16:34:00pending_verification18-24
779aidenaiden@example.com2026-09-09 14:53:00active25-34
876aria_baria_b@example.com2024-10-10inactive35-44
973brianbrian@example.com2025-11-11suspended45-54

Expected output

First 5 of 65 rows
view_dateinactive_viewssuspended_views
2022-01-0610
2022-06-1101
2022-09-2610
2026-04-0220
2026-09-0630

Worked solution and explanation

What this is really asking

This is a cross-grain conditional aggregation wearing a product-analytics costume. The status you split on, inactive versus suspended versus active, lives on users, but the views you count live on the 600M-row page_views fact, so the per-day totals only exist after the join. Here is the part that separates people: 'more than half of that day's views' makes COUNT(*) the denominator, and COUNT(*) counts whatever survives the WHERE. Scope it to the three account types you care about and the majority test means what you intend; forget to drop pending_verification and half-onboarded users silently pad the denominator, quietly flipping which days qualify. One more quiet trap: on this data the two lapsed segments never post views on the same day, so anyone who tries to pit inactive directly against suspended gets a column of zeros. The real comparison is the lapsed pair against the active users. And group on the raw timestamp instead of date(viewed_at) and every day shatters into per-second buckets that never clear the threshold.


Break down the requirements

Step 1: Join and scope the population

Inner join page_views to users on user_id, then restrict to account_status IN ('inactive','suspended','active') in WHERE. This defines the population whose views form the denominator of the majority test, and deliberately leaves out pending_verification accounts that are still onboarding. It must happen before aggregation.

Step 2: Pivot per day

Group by date(viewed_at) and use one SUM(CASE WHEN ...) per lapsed status. This collapses the day's rows into one row with two named columns, inactive_views and suspended_views, off a single scan.

Step 3: Filter on the aggregates

Test the majority in HAVING: the two lapsed segments together must exceed half the day's scoped total, written (SUM(CASE WHEN ... 'inactive' ...) + SUM(CASE WHEN ... 'suspended' ...)) * 2 > COUNT(*). Because WHERE scoped the group to active plus the two lapsed segments, this is exactly the lapsed pair out-drawing the active users. Order by view_date ascending so the earliest days surface first.


The solution

DAYS WHERE THE LAPSED SEGMENTS OUT-DREW ACTIVE USERS
SELECT date(pv.viewed_at) AS view_date,
       SUM(CASE WHEN u.account_status = 'inactive'  THEN 1 ELSE 0 END) AS inactive_views,
       SUM(CASE WHEN u.account_status = 'suspended' THEN 1 ELSE 0 END) AS suspended_views
FROM page_views pv
INNER JOIN users u ON pv.user_id = u.user_id
WHERE u.account_status IN ('inactive', 'suspended', 'active')
GROUP BY date(pv.viewed_at)
HAVING (SUM(CASE WHEN u.account_status = 'inactive'  THEN 1 ELSE 0 END)
      + SUM(CASE WHEN u.account_status = 'suspended' THEN 1 ELSE 0 END)) * 2 > COUNT(*)
ORDER BY view_date;

COMMON FOLLOW-UP QUESTIONS

Common follow-up questions