SQL Funnel Analysis Interview Questions
Practice distinct counts, stage reach, conversion rates, and drop-off checks.
Funnel questions test whether you can measure users or sessions at several
conversion stages without double-counting event rows. The key move is often
count(distinct ...), because one session can produce many event
records. If the business question requires a strict sequence, you also need
logic that verifies stage order rather than merely counting stage reach.
Before writing SQL, clarify the entity being measured: sessions, users, accounts, orders, or visitors. The same event table can answer different business questions depending on that grain.
Common funnel interview prompts
Answer each one yourself before you look: write your query or your reasoning in the box, or open the playground and run it. Then reveal the model answer and compare. Nothing you type is saved or sent anywhere.
-
Count how many sessions reached each funnel stage.
Try it in the playground →Reveal a model answer
select event_name, count(distinct session_id) as sessions from events group by event_name order by sessions desc, event_namecount(distinct session_id), not count(*): a session that fires add_to_cart twice reached the stage once.
-
Calculate purchase rate by marketing channel.
Try it in the playground →Reveal a model answer
select s.channel, count(distinct s.session_id) as sessions, count(distinct case when e.event_name = 'purchase' then s.session_id end) as purchasing_sessions, round(100.0 * count(distinct case when e.event_name = 'purchase' then s.session_id end) / count(distinct s.session_id), 1) as purchase_rate from sessions as s left join events as e on e.session_id = s.session_id group by s.channel order by purchase_rate desc, s.channelSessions are the base, so start there and LEFT JOIN events; channels with no purchases then show 0 instead of vanishing. 100.0 keeps the division fractional on engines that truncate integers.
-
Find the largest drop-off between signup, checkout, and purchase.
Try it in the playground →Reveal a model answer
with reach as ( select event_name, count(distinct session_id) as sessions from events where event_name in ('signup', 'checkout', 'purchase') group by event_name ), ordered as ( select event_name, sessions, lag(sessions) over ( order by case event_name when 'signup' then 1 when 'checkout' then 2 else 3 end ) as previous_stage from reach ) select event_name, previous_stage, sessions, previous_stage - sessions as drop_off from ordered where previous_stage is not null order by drop_off descPut the stages in funnel order with a CASE, then lag() compares each stage with the one before. Point out that this is stage reach, not a strict funnel: a session can check out without a recorded signup, so a later stage can even be larger.
-
Compare conversion for logged-in versus anonymous sessions.
Try it in the playground →Reveal a model answer
select case when s.user_id is null then 'anonymous' else 'logged in' end as visitor, count(distinct s.session_id) as sessions, count(distinct case when e.event_name = 'purchase' then s.session_id end) as purchasing_sessions from sessions as s left join events as e on e.session_id = s.session_id group by visitor order by visitorSegment with a CASE on user_id being NULL, keep sessions as the base, and count purchasing sessions per segment. Add the rate once both counts look right.
-
Explain why counting event rows can overstate conversion.
Reveal a model answer
One session can log the same event several times: a double click, a retried checkout. count(*) counts those records, while the question is how many sessions or users reached the stage. Count distinct entities, and state which entity the denominator uses.
select event_name,
count(distinct session_id) as sessions,
round(
100.0 * count(distinct session_id) /
(select count(*) from sessions),
1
) as pct_of_sessions
from events
group by event_name
order by sessions desc, event_name What matters
A stage-reach metric usually asks "did the entity record this event at least
once?" That means count(distinct session_id), not
count(*). Counting rows answers "how many event records exist",
which can overstate reach when sessions repeat actions. A true sequential
funnel needs additional ordering or timestamp logic.
For segmented conversion, keep the base table on the left side of the join.
Starting from sessions preserves channels with no purchases,
while starting from purchase events drops those zero-conversion groups.
select s.channel,
count(distinct s.session_id) as sessions,
count(distinct e.session_id) filter (
where e.event_name = 'purchase'
) as purchasing_sessions
from sessions as s
left join events as e on e.session_id = s.session_id
group by s.channel
order by purchasing_sessions desc, s.channel How to talk it through
Say: "The result is one row per channel. Sessions are the base population, so I will start from the sessions table, left join events, count distinct sessions overall, and count distinct sessions that reached purchase."
Call out the denominator. A percentage can use all sessions, sessions within a channel, or sessions that reached the previous funnel step. Those are different metrics.
Practice next
Work through the conversion stage reach, channel conversion mission, and retention cohort mission. Review integer division before writing conversion rates.
Quick answers
What should I clarify first in a SQL funnel interview question?
Clarify the entity being measured, such as sessions, users, accounts, or orders, because that choice controls the denominator.
Why do funnel queries often use COUNT DISTINCT?
A single session or user can create many event rows, so COUNT DISTINCT measures whether the entity reached a stage instead of counting every event.
How should I choose the denominator for conversion rate?
State whether the denominator is all starting sessions, sessions in the same segment, or sessions that reached the previous step.
Related
Build ranked summary reports.
Nested SELECTs, IN, EXISTS, and correlation.
Ranking, LAG/LEAD, and running totals at a glance.
The default RANGE frame groups tied rows; use ROWS.
Filter window results in a CTE or with QUALIFY.
Convert counts into rates.