The Blind Spot
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
- L5
- Asked in
- Meta
Problem
We run a content platform with a built-in team chat, and we want to recommend pages a person has not opened yet. Treat two people as connected when they have both posted in the same chat channel, and for each person surface the pages that at least two different connections have viewed but the person themselves never has.
Pages they haven't discovered yet.
Tables
page_views
Columns: view_id INTEGER, page_url TEXT, user_id INTEGER, referrer TEXT, dur_ms INTEGER, device TEXT, viewed_at TEXT
| view_id | page_url | user_id | referrer | dur_ms | device | viewed_at |
|---|---|---|---|---|---|---|
| 5053 | /products | 1555 | amazon.com | 637 | desktop | 2026-09-02 01:09:00 |
| 5106 | /checkout | 1652 | twitter.com | 774 | tablet | 2026-09-03 02:18:00 |
| 5159 | /profile | 1749 | github.com | 911 | Mobile | 2026-09-04 03:27:00 |
| 5212 | /search | 1846 | NULL | 1048 | DESKTOP | 2026-09-05 04:36:00 |
| 5265 | /about | 1943 | reddit.com | 1185 | mobile | 2026-09-06 05:45:00 |
| 5318 | /blog/intro | 1555 | NULL | NULL | desktop | 2026-09-07 06:54:00 |
| 5371 | /pricing | 1652 | bing.com | 1459 | tablet | 2026-09-08 07:03:00 |
| 5424 | /docs/api | 1749 | news.ycombinator.com | 1596 | Mobile | 2026-09-09 08:12:00 |
| 5477 | /settings | 1846 | NULL | 1733 | DESKTOP | 2026-09-10 09:21:00 |
| 5530 | /Home | 1943 | bing.com | 1870 | mobile | 2026-09-11 10:30:00 |
chat_msgs
Columns: msg_id INTEGER, channel TEXT, sender_id INTEGER, content TEXT, msg_type TEXT, sent_at TEXT, edited INTEGER, reply_to INTEGER
| msg_id | channel | sender_id | content | msg_type | sent_at | edited | reply_to |
|---|---|---|---|---|---|---|---|
| 147 | #engineering | 197 | Anyone seeing increased latency? | file | 2026-02-02 01:07:00 | 0 | NULL |
| 194 | #incidents | 294 | PR #412 ready for review | reaction | 2026-03-03 02:14:00 | 0 | NULL |
| 241 | #random | 391 | Dashboard is down | thread_reply | 2026-04-04 03:21:00 | 0 | NULL |
| 288 | #deployments | 488 | Fixed the flaky test | system | 2026-05-05 04:28:00 | 0 | NULL |
| 335 | #data-team | 585 | Merged the migration | Text | 2026-06-06 05:35:00 | 0 | NULL |
| 382 | #general | 682 | NULL | FILE | 2026-07-07 06:42:00 | 0 | NULL |
| 429 | #engineering | 779 | LGTM | text | 2026-08-08 07:49:00 | 0 | NULL |
| 476 | #incidents | 876 | 👍 | file | 2026-09-09 08:56:00 | 0 | 5 |
| 523 | #random | 973 | reaction | 2026-01-10 09:03:00 | 0 | NULL | |
| 570 | #deployments | 1070 | Can someone check the pipeline? | thread_reply | 2026-02-11 10:10:00 | 0 | NULL |
Expected output
| user_id | content_id |
|---|---|
| 197 | /Home |
| 197 | /checkout |
| 197 | /search |
| 197 | /about |
| 197 | /docs/api |
Worked solution and explanation
What this problem really is
This is a per-person set difference with a popularity gate, wearing a social-graph costume. The real question: take every page a person's chat connections have viewed, keep the ones at least two DIFFERENT connections saw, and subtract everything the person has already opened. Anyone can self-join chat_msgs to find who shares a channel. The hard part is proving 'two different connections' with no aggregate in the toolbox: judge popularity by counting connection-view rows and one connection who shares two channels with you, or who viewed the page five times, clears the bar alone. Then there is the exclusion: key it on user_id alone instead of the (user_id, content_id) pair and you delete pages other people viewed, not the ones this person did.
Break down the requirements
Step 1: Find each person's connections and what they viewed
connection_views self-joins chat_msgs to itself on the same channel where the two sender_id values differ, so a person is never paired with themselves, then joins to page_views on the connection's sender_id. Each row is (person, one of their connections, a page that connection viewed). Leave the rows undeduped here: the next step needs the full evidence.
Step 2: Prove two DIFFERENT connections, without counting
popular self-joins connection_views to itself on the same (user_id, content_id) where connection_id differs. A matching pair can only exist when two genuinely different connections both viewed that page for that person, so a single connection, no matter how many channels they share or how often they viewed, can never pair with themselves. SELECT DISTINCT then collapses the many surviving pairs down to one (person, page) row.
Step 3: Subtract the person's own views
LEFT JOIN popular back to page_views on BOTH user_id and page_url, then keep only the rows where the match is NULL. That NULL means this exact person never viewed this exact page, so it is a genuine recommendation.
The solution
WITH connection_views AS (
SELECT cm1.sender_id AS user_id,
cm2.sender_id AS connection_id,
pv.page_url AS content_id
FROM chat_msgs cm1
INNER JOIN chat_msgs cm2
ON cm1.channel = cm2.channel
AND cm1.sender_id <> cm2.sender_id
INNER JOIN page_views pv
ON pv.user_id = cm2.sender_id
),
popular AS (
SELECT DISTINCT cv1.user_id, cv1.content_id
FROM connection_views cv1
INNER JOIN connection_views cv2
ON cv1.user_id = cv2.user_id
AND cv1.content_id = cv2.content_id
AND cv1.connection_id <> cv2.connection_id
)
SELECT p.user_id, p.content_id
FROM popular p
LEFT JOIN page_views own
ON own.user_id = p.user_id
AND own.page_url = p.content_id
WHERE own.page_url IS NULLCommon follow-up questions
- How would you rank each person's recommendations by how many connections viewed the page? (Introduces an aggregate: count the DISTINCT connections per (person, page) and sort descending, a natural extension of the inequality self-join.)
- How would you keep only the top 5 recommendations per person? (Tests `ROW_NUMBER` partitioned by user with a rank filter after the anti-join.)
- How would this hold up with 100M people and daily refreshes? (Discusses materialized views, incremental refresh, and moving the connection graph to a graph store.)
- What changes if connections are only people who directly replied to each other? (Tests using `reply_to` to build a directed reply edge instead of channel co-participation.)