Topic 59 of

Funnel Analysis: Optimize Your Conversion Flow

85% of Flipkart users add to cart, but only 12% complete checkout. Funnel analysis reveals where users drop off — and how to fix it.

📚Intermediate
⏱️11 min
10 quizzes
🔻

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:

  1. Quantify impact — "Fixing checkout saves 5K orders/month (50% drop-off × 10K users)"
  2. Prioritize fixes — Focus on biggest drop-offs (highest ROI)
  3. Measure improvements — A/B test: Did new checkout increase conversion 40% → 45%?
  4. Segment analysis — Do mobile users drop off more than desktop?
  5. 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
Think of it this way...

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:

query.sqlSQL
events (user_id, event_type, event_timestamp, session_id)

Query (Session-based funnel):

query.sqlSQL
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 funnel

Output:

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).

query.sqlSQL
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).

query.sqlSQL
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)

code.pyPython
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

code.pyPython
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)

code.pyPython
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:

query.sqlSQL
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?):

query.sqlSQL
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).

query.sqlSQL
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}