What is Funnel Analysis?
Funnel analysis tracks user progression through sequential steps to measure conversion rates and identify where users drop off.
How It Works
1. Define Funnel Steps (Sequential stages):
E-commerce Purchase Funnel:
1. Product page view → 100,000 users
2. Add to cart → 25,000 users (25% conversion)
3. Checkout initiated → 10,000 users (10% overall, 40% of cart adds)
4. Payment info entered → 7,000 users (7% overall, 70% of checkouts)
5. Order completed → 6,000 users (6% overall, 86% of payments)
2. Calculate Conversion Rates:
Step-to-Step Conversion:
View → Cart: 25% (25K / 100K)
Cart → Checkout: 40% (10K / 25K)
Checkout → Payment: 70% (7K / 10K)
Payment → Complete: 86% (6K / 7K)
Overall Conversion: 6% (6K completed / 100K started)
3. Identify Drop-off Points:
Biggest Drop-offs:
1. View → Cart: 75% drop-off (75K users lost)
2. Cart → Checkout: 60% drop-off (15K users lost)
Action: Focus on these two steps (highest impact)
Why Funnel Analysis Matters
Without Funnel Analysis:
"Sales are down 10% this month"
→ Why? (Can't tell)
→ What to fix? (No idea)
With Funnel Analysis:
Funnel comparison (This month vs Last month):
View → Cart: 25% → 22% (-3%, 12% relative drop)
Cart → Checkout: 40% → 38% (-2%, 5% relative drop)
Payment → Complete: 86% → 85% (-1%, 1% drop)
Root cause: "Add to Cart" conversion dropped (25% → 22%)
Investigate: Page load speed? Out-of-stock items? Pricing changes?
Fix: Found slow loading (3s → 1.5s after optimization)
Result: Cart conversion recovered (22% → 26%, +4%)
Benefits:
- Quantify impact — "Fixing checkout saves 5K orders/month (50% drop-off × 10K users)"
- Prioritize fixes — Focus on biggest drop-offs (highest ROI)
- Measure improvements — A/B test: Did new checkout increase conversion 40% → 45%?
- Segment analysis — Do mobile users drop off more than desktop?
- Predict revenue — If cart conversion increases 5%, revenue increases ₹50L/month
Real Example: Swiggy Order Funnel
Goal: Optimize food ordering funnel.
Funnel Steps:
1. Restaurant page view → 500,000 users
2. Menu browse → 400,000 users (80% conversion)
3. Add item to cart → 200,000 users (40% overall, 50% of browsers)
4. Checkout initiated → 100,000 users (20% overall, 50% of cart adds)
5. Address entered → 80,000 users (16% overall, 80% of checkouts)
6. Payment successful → 70,000 users (14% overall, 87.5% of addresses)
Analysis:
Biggest Drop-offs:
1. Restaurant → Menu: 20% drop (100K users) — MINOR (natural browse/exit)
2. Menu → Cart: 50% drop (200K users) — MAJOR (200K users didn't add items)
3. Cart → Checkout: 50% drop (100K users) — MAJOR (abandoned carts)
4. Address → Payment: 12.5% drop (10K users) — MINOR
Root Cause Investigation:
Menu → Cart (50% drop):
- Hypothesis: Menu images unclear, prices too high, out-of-stock items
- Data: Users scroll 80% of menu but don't add (engagement present, friction exists)
- Fix: A/B test larger food images, show "popular items" banner
- Result: Cart conversion 50% → 58% (+16% relative lift)
Cart → Checkout (50% drop):
- Hypothesis: Delivery fee surprise, minimum order not met, slow cart page
- Data: 60% of drop-offs occur within 10s of cart page load (fast exit = friction)
- Fix: Show delivery fee estimate on menu page (before cart), highlight minimum order early
- Result: Checkout conversion 50% → 55% (+10% relative lift)
Impact:
Before optimizations: 14% overall conversion (70K orders from 500K views)
After optimizations: 18.8% overall conversion (94K orders from 500K views)
Increase: +24K orders/day × ₹400 avg order = ₹9.6L additional daily revenue
Annual impact: ₹350 Cr
Funnel analysis is like a leaky pipe with multiple holes. Water enters at 100L/min, but only 6L/min reaches the end (94L leaked). Funnel analysis identifies WHERE leaks are biggest (75L leak at hole 1, 15L at hole 2) so you prioritize fixing hole 1 first (highest impact).
Building Funnels with SQL
Basic Funnel Query
Schema:
events (user_id, event_type, event_timestamp, session_id)Query (Session-based funnel):
WITH funnel_events AS (
SELECT
session_id,
MAX(CASE WHEN event_type = 'product_view' THEN 1 ELSE 0 END) AS viewed_product,
MAX(CASE WHEN event_type = 'add_to_cart' THEN 1 ELSE 0 END) AS added_to_cart,
MAX(CASE WHEN event_type = 'checkout_start' THEN 1 ELSE 0 END) AS started_checkout,
MAX(CASE WHEN event_type = 'payment_info' THEN 1 ELSE 0 END) AS entered_payment,
MAX(CASE WHEN event_type = 'order_complete' THEN 1 ELSE 0 END) AS completed_order
FROM events
WHERE DATE(event_timestamp) = '2026-03-22'
GROUP BY session_id
)
SELECT
COUNT(*) AS total_sessions,
SUM(viewed_product) AS step1_product_view,
SUM(added_to_cart) AS step2_add_to_cart,
SUM(started_checkout) AS step3_checkout,
SUM(entered_payment) AS step4_payment,
SUM(completed_order) AS step5_complete,
-- Conversion rates (step-to-step)
ROUND(100.0 * SUM(added_to_cart) / NULLIF(SUM(viewed_product), 0), 1) AS view_to_cart_pct,
ROUND(100.0 * SUM(started_checkout) / NULLIF(SUM(added_to_cart), 0), 1) AS cart_to_checkout_pct,
ROUND(100.0 * SUM(entered_payment) / NULLIF(SUM(started_checkout), 0), 1) AS checkout_to_payment_pct,
ROUND(100.0 * SUM(completed_order) / NULLIF(SUM(entered_payment), 0), 1) AS payment_to_complete_pct,
-- Overall conversion
ROUND(100.0 * SUM(completed_order) / NULLIF(SUM(viewed_product), 0), 1) AS overall_conversion_pct
FROM funnel_events
WHERE viewed_product = 1; -- Only sessions that started funnelOutput:
total_sessions | step1 | step2 | step3 | step4 | step5 | view_to_cart | cart_to_checkout | ...
100000 | 100K | 25K | 10K | 7K | 6K | 25.0 | 40.0 | ...
Time-Constrained Funnel
Scenario: Measure funnel completion within 1 hour (users who abandon >1hr excluded).
WITH step_times AS (
SELECT
session_id,
MIN(CASE WHEN event_type = 'product_view' THEN event_timestamp END) AS view_time,
MIN(CASE WHEN event_type = 'add_to_cart' THEN event_timestamp END) AS cart_time,
MIN(CASE WHEN event_type = 'checkout_start' THEN event_timestamp END) AS checkout_time,
MIN(CASE WHEN event_type = 'order_complete' THEN event_timestamp END) AS complete_time
FROM events
WHERE DATE(event_timestamp) = '2026-03-22'
GROUP BY session_id
),
constrained_funnel AS (
SELECT
session_id,
CASE WHEN view_time IS NOT NULL THEN 1 ELSE 0 END AS step1,
CASE WHEN cart_time <= view_time + INTERVAL '1 hour' THEN 1 ELSE 0 END AS step2,
CASE WHEN checkout_time <= cart_time + INTERVAL '1 hour' THEN 1 ELSE 0 END AS step3,
CASE WHEN complete_time <= checkout_time + INTERVAL '1 hour' THEN 1 ELSE 0 END AS step4
FROM step_times
WHERE view_time IS NOT NULL
)
SELECT
SUM(step1) AS viewed_product,
SUM(step2) AS added_to_cart_1hr,
SUM(step3) AS checkout_1hr,
SUM(step4) AS completed_1hr,
ROUND(100.0 * SUM(step4) / SUM(step1), 1) AS overall_conversion_1hr_pct
FROM constrained_funnel;Use Case: E-commerce funnels often have 1-hour constraint (users who take >1hr are browsing, not converting).
Segmented Funnel Analysis
Compare funnels across segments (mobile vs desktop, paid vs organic traffic).
WITH funnel_events AS (
SELECT
e.session_id,
e.device_type, -- 'mobile', 'desktop'
MAX(CASE WHEN e.event_type = 'product_view' THEN 1 ELSE 0 END) AS viewed,
MAX(CASE WHEN e.event_type = 'add_to_cart' THEN 1 ELSE 0 END) AS carted,
MAX(CASE WHEN e.event_type = 'order_complete' THEN 1 ELSE 0 END) AS completed
FROM events e
WHERE DATE(e.event_timestamp) = '2026-03-22'
GROUP BY e.session_id, e.device_type
)
SELECT
device_type,
SUM(viewed) AS step1_views,
SUM(carted) AS step2_carts,
SUM(completed) AS step3_orders,
ROUND(100.0 * SUM(carted) / NULLIF(SUM(viewed), 0), 1) AS view_to_cart_pct,
ROUND(100.0 * SUM(completed) / NULLIF(SUM(carted), 0), 1) AS cart_to_order_pct,
ROUND(100.0 * SUM(completed) / NULLIF(SUM(viewed), 0), 1) AS overall_conversion_pct
FROM funnel_events
WHERE viewed = 1
GROUP BY device_type;Output:
device_type | step1 | step2 | step3 | view_to_cart | cart_to_order | overall
mobile | 60K | 12K | 3K | 20.0 | 25.0 | 5.0
desktop | 40K | 13K | 5K | 32.5 | 38.5 | 12.5
Insight: Desktop converts 2.5× better (12.5% vs 5%) — optimize mobile UX
⚠️ CheckpointQuiz error: Missing or invalid options array
Visualizing Funnels with Python
Funnel Chart (Classic Drop-off Visualization)
import pandas as pd
import plotly.graph_objects as go
# Funnel data
funnel_data = {
'Stage': ['Product View', 'Add to Cart', 'Checkout', 'Payment', 'Complete'],
'Users': [100000, 25000, 10000, 7000, 6000],
'Conversion': [100.0, 25.0, 10.0, 7.0, 6.0]
}
df = pd.DataFrame(funnel_data)
# Plotly funnel chart
fig = go.Figure(go.Funnel(
y=df['Stage'],
x=df['Users'],
textposition="inside",
textinfo="value+percent initial",
marker=dict(color=['#4CAF50', '#8BC34A', '#CDDC39', '#FFEB3B', '#FFC107']),
connector={"line": {"color": "#999", "dash": "dot", "width": 2}}
))
fig.update_layout(
title='E-commerce Purchase Funnel',
showlegend=False,
width=800,
height=500
)
fig.show()Output: Funnel chart showing progressive narrowing (100K → 6K).
Drop-off Bar Chart
import matplotlib.pyplot as plt
import numpy as np
stages = ['View→Cart', 'Cart→Checkout', 'Checkout→Payment', 'Payment→Complete']
step_conversion = [25.0, 40.0, 70.0, 86.0]
drop_off = [100 - x for x in step_conversion]
fig, ax = plt.subplots(figsize=(10, 6))
x = np.arange(len(stages))
width = 0.35
bars1 = ax.bar(x - width/2, step_conversion, width, label='Converted', color='#4CAF50')
bars2 = ax.bar(x + width/2, drop_off, width, label='Dropped Off', color='#F44336')
ax.set_xlabel('Funnel Step')
ax.set_ylabel('Percentage')
ax.set_title('Step-to-Step Conversion vs Drop-off')
ax.set_xticks(x)
ax.set_xticklabels(stages, rotation=15, ha='right')
ax.legend()
ax.set_ylim(0, 100)
# Add percentage labels
for bar in bars1:
height = bar.get_height()
ax.text(bar.get_x() + bar.get_width()/2., height,
f'{height:.0f}%', ha='center', va='bottom', fontsize=10)
plt.tight_layout()
plt.show()Output: Side-by-side bars showing conversion (green) vs drop-off (red) for each step.
Sankey Diagram (Flow Between Steps)
import plotly.graph_objects as go
# Define flows (source → target, value)
labels = ['View', 'Cart', 'Checkout', 'Payment', 'Complete', 'Drop-off']
source = [0, 1, 2, 3, 0, 1, 2, 3] # From stages
target = [1, 2, 3, 4, 5, 5, 5, 5] # To stages or drop-off
values = [25000, 10000, 7000, 6000, 75000, 15000, 3000, 1000] # Flow volumes
fig = go.Figure(go.Sankey(
node=dict(
pad=15,
thickness=20,
line=dict(color='black', width=0.5),
label=labels,
color=['#4CAF50', '#8BC34A', '#CDDC39', '#FFEB3B', '#FFC107', '#F44336']
),
link=dict(
source=source,
target=target,
value=values,
color=['rgba(76,175,80,0.4)' if t != 5 else 'rgba(244,67,54,0.4)' for t in target]
)
))
fig.update_layout(
title='Funnel Flow Analysis (100K Sessions)',
font=dict(size=12),
width=900,
height=600
)
fig.show()Output: Sankey diagram showing flow from each stage to next stage (green) or drop-off (red).
Advanced Funnel Analysis Techniques
1. Time-to-Convert Analysis
Measure how long users take to progress through funnel.
SQL Query:
WITH funnel_times AS (
SELECT
session_id,
MIN(CASE WHEN event_type = 'product_view' THEN event_timestamp END) AS view_time,
MIN(CASE WHEN event_type = 'add_to_cart' THEN event_timestamp END) AS cart_time,
MIN(CASE WHEN event_type = 'order_complete' THEN event_timestamp END) AS complete_time
FROM events
WHERE DATE(event_timestamp) = '2026-03-22'
GROUP BY session_id
),
time_diffs AS (
SELECT
session_id,
EXTRACT(EPOCH FROM (cart_time - view_time)) / 60 AS view_to_cart_mins,
EXTRACT(EPOCH FROM (complete_time - cart_time)) / 60 AS cart_to_complete_mins,
EXTRACT(EPOCH FROM (complete_time - view_time)) / 60 AS overall_mins
FROM funnel_times
WHERE view_time IS NOT NULL AND complete_time IS NOT NULL
)
SELECT
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY view_to_cart_mins) AS median_view_to_cart_mins,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY cart_to_complete_mins) AS median_cart_to_complete_mins,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY overall_mins) AS median_overall_mins
FROM time_diffs;Output:
Median time to add to cart: 3.2 minutes
Median time from cart to complete: 8.5 minutes
Median overall conversion time: 12.7 minutes
Insight: Users decide to cart quickly (3 mins), but checkout takes 8+ mins (friction point)
2. Reverse Funnel Analysis
Goal: Work backwards from converters to identify success patterns.
Query (What actions do converters take that non-converters don't?):
WITH converters AS (
SELECT DISTINCT session_id
FROM events
WHERE event_type = 'order_complete'
AND DATE(event_timestamp) = '2026-03-22'
),
session_actions AS (
SELECT
e.session_id,
CASE WHEN c.session_id IS NOT NULL THEN 1 ELSE 0 END AS converted,
COUNT(DISTINCT CASE WHEN e.event_type = 'product_view' THEN e.product_id END) AS products_viewed,
COUNT(DISTINCT CASE WHEN e.event_type = 'search' THEN 1 END) AS searches,
COUNT(DISTINCT CASE WHEN e.event_type = 'filter_click' THEN 1 END) AS filter_uses,
MAX(CASE WHEN e.event_type = 'review_read' THEN 1 ELSE 0 END) AS read_reviews
FROM events e
LEFT JOIN converters c ON e.session_id = c.session_id
WHERE DATE(e.event_timestamp) = '2026-03-22'
GROUP BY e.session_id, converted
)
SELECT
converted,
AVG(products_viewed) AS avg_products_viewed,
AVG(searches) AS avg_searches,
AVG(filter_uses) AS avg_filters,
SUM(read_reviews) * 100.0 / COUNT(*) AS pct_read_reviews
FROM session_actions
GROUP BY converted;Output:
| Converters | Non-converters
Products viewed | 5.2 | 2.8 (converters view 2× more)
Searches | 1.8 | 1.2 (converters search 50% more)
Filter uses | 3.1 | 0.9 (converters filter 3× more)
Read reviews | 68% | 22% (converters 3× more likely to read)
Insight: Converters engage deeply (more views, filters, reviews) — optimize for exploration
3. Multi-Path Funnel Analysis
Scenario: Users take different paths (direct checkout vs add-to-wishlist-first vs guest checkout).
WITH user_paths AS (
SELECT
session_id,
STRING_AGG(event_type, ' → ' ORDER BY event_timestamp) AS path
FROM events
WHERE DATE(event_timestamp) = '2026-03-22'
GROUP BY session_id
),
path_outcomes AS (
SELECT
up.path,
COUNT(*) AS sessions,
SUM(CASE WHEN up.path LIKE '%order_complete%' THEN 1 ELSE 0 END) AS conversions,
ROUND(100.0 * SUM(CASE WHEN up.path LIKE '%order_complete%' THEN 1 ELSE 0 END) / COUNT(*), 1) AS conversion_rate
FROM user_paths up
GROUP BY up.path
HAVING COUNT(*) >= 100 -- Only paths with 100+ sessions
ORDER BY sessions DESC
LIMIT 20
)
SELECT * FROM path_outcomes;Output:
path | sessions | conversions | conversion_rate
view → cart → checkout → payment → complete | 8500 | 7200 | 84.7
view → cart → continue_shopping → ... | 12000 | 1200 | 10.0
view → wishlist → view → cart → ... | 3200 | 2100 | 65.6
Insight: Direct path (view → cart → checkout) converts best (84.7%)
Wishlist path converts 65.6% (still good, users return later)
4. Cohorted Funnel Analysis
Compare funnels across time cohorts (before/after product change).
Example — Swiggy: Before/After New Checkout UI:
Before (Jan 1-15):
View → Cart: 22%
Cart → Checkout: 45%
Checkout → Complete: 78%
Overall: 7.7%
After (Jan 16-31):
View → Cart: 22% (unchanged)
Cart → Checkout: 52% (+7%, +16% relative)
Checkout → Complete: 82% (+4%, +5% relative)
Overall: 9.4% (+1.7%, +22% relative)
Conclusion: New checkout UI increased overall conversion 22% (7.7% → 9.4%)
⚠️ FinalQuiz error: Missing or invalid questions array
⚠️ SummarySection error: Missing or invalid items array
Received: {"hasItems":false,"isArray":false}