Love and Loathing
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
We run a feature-flag platform where every flag has an owner team, an on/off state, and a rollout percentage. Line up the enabled flags from the highest rollout down and the disabled flags from the lowest rollout up, then pair them off by position: the top enabled flag with the top disabled flag, the second with the second, and so on. Break ties within each side by the lower flag ID first, and return each pair's two owner teams.
Some teams champion the flag. Others would kill it.
Tables
feat_flags
Columns: flag_id INTEGER, flag_name TEXT, enabled INTEGER, rollout REAL, owner TEXT, created TEXT, updated TEXT
| flag_id | flag_name | enabled | rollout | owner | created | updated |
|---|---|---|---|---|---|---|
| 131 | new_checkout | 0 | NULL | engineering | 2025-02-02 | 2026-02-02 |
| 162 | beta_search | 0 | NULL | growth | 2025-03-03 | 2026-03-03 |
| 193 | ai_recs | 1 | 39 | platform | 2025-04-04 | 2026-04-04 |
| 224 | live_chat | 0 | NULL | mobile | 2025-05-05 | 2026-05-05 |
| 255 | video_upload | 0 | NULL | data | 2025-06-06 | 2026-06-06 |
| 286 | two_factor | 1 | 78 | product | 2025-07-07 | 2026-07-07 |
| 317 | bulk_export | 0 | NULL | engineering | 2025-08-08 | 2026-08-08 |
| 348 | new_dashboard | 0 | NULL | growth | 2025-09-09 | 2026-09-09 |
| 379 | mobile_v2 | 1 | 17 | platform | 2025-10-10 | 2026-01-10 |
| 410 | dark_mode | 0 | NULL | mobile | 2025-11-11 | 2026-02-11 |
Expected output
| fan_owner | opponent_owner |
|---|---|
| platform | engineering |
| platform | growth |
| platform | mobile |
| product | engineering |
| product | growth |
Worked solution and explanation
Why this problem exists in real interviews
This is a positional zip of two oppositely-sorted groups that share no natural join key. Nothing in the data links an enabled flag to its opposite-rank disabled counterpart, so you have to manufacture the key yourself with ROW_NUMBER() over each filtered slice and then equi-join on that position. The trap: reach for RANK or DENSE_RANK and the moment two flags tie on rollout they collect the same rank, the position join goes many-to-many, and one clean pair explodes into a grid of junk rows. Get it wrong and the top fan lands against the wrong detractor, or the output Cartesian-multiplies.
Break down the requirements
Step 1: Split the universe in two
Two CTEs over feat_flags, one with WHERE enabled = 1, one with WHERE enabled = 0. Same table, two filtered slices. Don't try to do this in a single window with PARTITION BY enabled, you'd still need to align positions across partitions.
Step 2: Rank each side oppositely
Fans: ROW_NUMBER() OVER (ORDER BY rollout DESC, flag_id ASC). Detractors: ROW_NUMBER() OVER (ORDER BY rollout ASC, flag_id ASC). The flag_id ASC tiebreaker is required, not optional, since rollout is not unique.
Step 3: Zip on the rank
INNER JOIN ... ON f.rn = o.rn. The join key is the position, not any business column. This is the move people miss: rank becomes a synthetic primary key for alignment.
Step 4: Order the output
Final ORDER BY f.rn so pair #1 is on top. Without it the planner may surface pairs in hash-join order, which looks random to a reviewer.
The solution
WITH fans AS (
SELECT owner,
ROW_NUMBER() OVER (ORDER BY rollout DESC, flag_id ASC) AS rn
FROM feat_flags
WHERE enabled = 1
),
opponents AS (
SELECT owner,
ROW_NUMBER() OVER (ORDER BY rollout ASC, flag_id ASC) AS rn
FROM feat_flags
WHERE enabled = 0
)
SELECT f.owner AS fan_owner,
o.owner AS opponent_owner
FROM fans f
INNER JOIN opponents o ON f.rn = o.rn
ORDER BY f.rn;- `DENSE_RANK` with a 3-way tie at rollout=80
- fans: (A,1) (B,1) (C,1) (D,2) detractors: (W,1) (X,1) (Y,1) (Z,2) Join on rank=1 produces 9 rows: A-W, A-X, A-Y, B-W, B-X, B-Y, C-W, C-X, C-Y. Not what you want.
- `ROW_NUMBER` with `flag_id` tiebreaker
- fans: (A,1) (B,2) (C,3) (D,4) detractors: (W,1) (X,2) (Y,3) (Z,4) Join on rn produces 4 rows: A-W, B-X, C-Y, D-Z. Clean 1:1 zip, deterministic across runs.
COMMON FOLLOW-UP QUESTIONS
Common follow-up questions
- What changes if fans and detractors have different counts and the product wants every fan represented? (Tests whether the candidate sees `INNER JOIN` as data-lossy and reaches for `LEFT JOIN` from the side that must survive.)
- How would you adapt this to pair the top-N fans against the top-N detractors per `owner` team? (Probes whether they can add `PARTITION BY owner` to both windows and still keep the positional join semantics intact.)
- If `rollout` had no tiebreaker available at all, how would you make the result deterministic? (Forces the conversation about whether you fabricate a tiebreaker (`flag_name`, hash of row) or accept non-determinism and document it.)
- Why not `FULL OUTER JOIN` on `rn` and report unpaired rows separately? (Tests engine awareness (SQLite lacks native `FULL OUTER` until 3.39) and product framing of what 'unpaired' means in this report.)