Fastest Page View to Click

A hard SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.

Domain
SQL
Difficulty
hard
Seniority
L5
Asked in
Meta

Problem

Return the three quickest browse-to-click transitions in event_data, from the shortest gap to the longest with a tied gap going to the earlier browsing event, each with that event's type and user_id, both timestamps, the gap in seconds, and the average gap across every transition in the stream. A transition is a 'page_view', 'search' or 'open' event whose very next event in the whole stream, from any user and of any type, is a 'button_click'.

How fast from view to click?

Tables

event_data

Columns: event_id INTEGER, event_type TEXT, user_id INTEGER, event_timestamp TEXT, payload TEXT, tags TEXT, properties TEXT

event_data: 10 of 200 rows
event_idevent_typeuser_idevent_timestamppayloadtagsproperties
3059button_click1002026-02-02 01:07:00{"button_id": "cancel", "element_class": "btn-primary", "position": {"x": 13, "y": 7}}["desktop", "web", "returning"]{"os": "Edge", "language": "US", "screen_size": "en", "utm_source": "mobile"}
3118purchase1972026-03-03 02:14:00{"amount": 56.93, "currency": "GBP", "items": [{"sku": "SKU1002", "qty": 3}]}["ios", "premium", "new_user", "organic"]{"country": "Windows", "device_type": "FR", "referrer": "fr", "utm_medium": "1920x1080", "browser": "twitter"}
3177signup2942026-04-04 03:21:00{"signup_method": "apple", "referral_code": "REF3X", "accepted_terms": true}["android", "free", "campaign_a", "referral", "experiment_v1"]{"language": "iOS", "screen_size": "es", "utm_source": "desktop", "session_id": "bing", "os": "organic"}
3236login3912026-05-05 04:28:00{"login_method": "password", "remember_me": true, "mfa_used": false}["web", "returning"]{"device_type": "JP", "referrer": "ja", "utm_medium": "1440x900"}
3295logout4882026-06-06 05:35:00{"action": "logout", "value": 50}["premium", "new_user", "organic"]{"screen_size": "de", "utm_source": "tablet", "session_id": "amazon", "os": "Chrome"}
3354search5852026-07-07 06:42:00{"query": "phone", "results_count": 102, "filters": {"category": "electronics"}}["free", "campaign_a", "referral", "experiment_v1"]{"referrer": "mobile", "utm_medium": "375x812", "browser": "email", "country": "Edge", "device_type": "US"}
3413add_to_cartNULL2026-08-08 07:49:00{"product_id": 1007, "quantity": 2, "price": 58.49, "variant": "medium"}["returning", "campaign_b", "featured", "experiment_v2", "ios"]{"utm_source": "fr", "session_id": "twitter", "os": "Safari", "language": "Windows", "screen_size": "FR"}
3472open7792026-09-09 08:56:00{"action": "open", "value": 80}["new_user", "organic"]{"utm_medium": "bing", "browser": "organic", "country": "iOS"}
3531crash8762026-01-10 09:03:00{"action": "crash", "value": 90}["campaign_a", "referral", "experiment_v1"]{"session_id": "direct", "os": "Firefox", "language": "macOS", "screen_size": "JP"}
3590error9732026-02-11 10:10:00{"action": "error", "value": 100}["campaign_b", "featured", "experiment_v2", "ios"]{"browser": "Chrome", "country": "Android", "device_type": "DE", "referrer": "de", "utm_medium": "tablet"}

Expected output

First 3 of 3 rows
user_idbrowse_eventbrowse_timeclick_timegap_secondsavg_gap_seconds
NULLpage_view2026-07-15 22:10:002026-07-16 15:45:0063300.00000894076749963.999994099
NULLpage_view2026-02-01 04:16:002026-02-02 01:07:0075059.999991953376749963.999994099
NULLpage_view2026-03-01 08:32:002026-03-02 05:23:0075059.999991953376749963.999994099

Worked solution and explanation

What this really is

This is a one-step lookahead over a shared stream, dressed up as engagement analytics. Everyone reaches for LEAD. What separates candidates is deciding what LEAD is allowed to see. 'Next' means the literal next event, any type, any user. Filter the stream down to browse and click events first and a click that came after a purchase suddenly pairs with an earlier search. Skip the browse-type check and every login or error sitting before a click joins the podium. Either way the three rows and the average describe the wrong events.

Building it

Step 1: Look ahead over the whole stream

In event_stream, take two LEAD lookups over the same ORDER BY event_timestamp: one for next_type, one for next_ts. No WHERE here and no PARTITION BY user_id: the stream is shared, and an intervening purchase has to stay visible so it can block a pair.

Step 2: Keep browse events that a click follows

In transitions, keep rows whose event_type is one of the three browsing types and whose next_type is 'button_click'. The user_id carried along is the browser's, which is what the output asks for.

Step 3: Turn the difference into seconds

(JULIANDAY(next_ts) - JULIANDAY(event_timestamp)) * 86400 gives elapsed seconds and survives midnight and year boundaries. Forget the * 86400 and a two-second gap reads as a tiny fraction of a day.

Step 4: Average the whole set, then cut to three

(SELECT AVG(gap_seconds) FROM transitions) reads the full CTE, so it ignores the LIMIT 3 on the outer query. Sort by gap_seconds ascending, let the earlier browse_time win a tied gap, and keep three rows.

Unfiltered lookahead, browse filter after
WITH event_stream AS (
  SELECT user_id,
    event_type,
    event_timestamp,
    LEAD(event_type) OVER (ORDER BY event_timestamp) AS next_type,
    LEAD(event_timestamp) OVER (ORDER BY event_timestamp) AS next_ts
  FROM event_data
),
transitions AS (
  SELECT user_id,
    event_type AS browse_event,
    event_timestamp AS browse_time,
    next_ts AS click_time,
    (JULIANDAY(next_ts) - JULIANDAY(event_timestamp)) * 86400 AS gap_seconds
  FROM event_stream
  WHERE event_type IN ('page_view', 'search', 'open')
    AND next_type = 'button_click'
)
SELECT user_id,
  browse_event,
  browse_time,
  click_time,
  gap_seconds,
  (SELECT AVG(gap_seconds) FROM transitions) AS avg_gap_seconds
FROM transitions
ORDER BY gap_seconds ASC, browse_time ASC
LIMIT 3
Filter the stream first
Putting the IN list inside event_stream hides purchases and logins from LEAD. A search, then a purchase, then a click becomes a transition it never was, and its gap lands in the average.
Filter the starting row
LEAD sees every event, so an intervening purchase blocks the pair. The IN list then only decides which rows may start a transition.

Common follow-up questions