Module 2: Funnels And Conversion

Finding the biggest drop-off

Overview

A step's drop-off is the number of users lost relative to the previous step (dropOff = users_step_N-1 - users_step_N), and the drop-off rate is that loss expressed as a proportion of the previous step (dropOffRate = dropOff / users_step_N-1, which is exactly 1 - conversionRate). This lesson brings together everything you built in lessons 3 and 4 —rate per step, overall conversion— into a single function, funnelAnalysis(), that also identifies the step with the highest dropOffRate: the one that, proportionally, lets the most people slip through its holes. That step is the funnel's highest-potential-impact lever.

How this connects to the module. This lesson explicitly and automatically answers the question left open since the module introduction, one lesson 3 started answering by hand: which step has the worst leak proportion? Here you don't calculate it by reading a table — the code calculates it, with a clear rule (highest dropOffRate), and the result is the direct foundation for the module's final project.

An everyday analogy: the strainer's biggest hole, not the level with the most water on top

Going back to the stacked strainer: if you want to fix the strainer so more water reaches the bottom, it makes no sense to focus on the level that has the most water on top of it (that level will lose more water in liters almost by definition, just for having more to start with). What you want to find is the level whose hole is proportionally the biggest — the one that lets through the smallest fraction of the water that reaches it. That's the level that, if you fix it, will improve the final result the most, no matter how much water it had on top to begin with. dropOffRate is exactly the measure of "how big is this level's hole, relative to what reached it" — not "how much water was lost here in liters".

Worked example: the full funnelAnalysis() over Mercado's checkout

// L5: full funnelAnalysis -- conversionRate and dropOff per step, overall conversion,
// and the step with the highest dropOffRate (the biggest lever).
function funnelAnalysis(steps) {
  const withRates = steps.map((step, i) => {
    if (i === 0) {
      return { name: step.name, users: step.users, conversionRate: null, dropOff: 0, dropOffRate: null };
    }
    const prevUsers = steps[i - 1].users;
    const conversionRate = step.users / prevUsers;
    const dropOff = prevUsers - step.users;
    const dropOffRate = dropOff / prevUsers;
    return { name: step.name, users: step.users, conversionRate, dropOff, dropOffRate };
  });

  const overallConversion = steps[steps.length - 1].users / steps[0].users;
  const worstStep = withRates.slice(1).reduce((worst, s) => (s.dropOffRate > worst.dropOffRate ? s : worst));

  return { steps: withRates, overallConversion, worstStep: worstStep.name, worstDropOffRate: worstStep.dropOffRate };
}

const funnel = [
  { name: 'visit', users: 10000 },
  { name: 'view_product', users: 6000 },
  { name: 'add_to_cart', users: 2400 },
  { name: 'checkout', users: 1200 },
  { name: 'purchase', users: 900 },
];

const result = funnelAnalysis(funnel);

console.log('=== funnelAnalysis() over Mercado\'s checkout ===\n');
result.steps.forEach((s) => {
  const conv = s.conversionRate === null ? '—' : (s.conversionRate * 100).toFixed(1) + '%';
  const drop = s.dropOffRate === null ? '—' : (s.dropOffRate * 100).toFixed(1) + '%';
  console.log(
    '  ' + s.name.padEnd(14) +
    String(s.users).padStart(6) + ' users' +
    '  conversionRate: ' + conv.padStart(6) +
    '  dropOff: ' + String(s.dropOff).padStart(5) +
    '  dropOffRate: ' + drop.padStart(6)
  );
});
console.log('\n  Overall conversion (visit -> purchase): ' + (result.overallConversion * 100).toFixed(1) + '%');
console.log('  Step with the biggest leak: ' + result.worstStep + ' (dropOffRate: ' + (result.worstDropOffRate * 100).toFixed(1) + '%)');

What to expect. When you run the file with Node, the output is exactly this:

=== funnelAnalysis() over Mercado's checkout ===

  visit          10000 users  conversionRate:      —  dropOff:     0  dropOffRate:      —
  view_product    6000 users  conversionRate:  60.0%  dropOff:  4000  dropOffRate:  40.0%
  add_to_cart     2400 users  conversionRate:  40.0%  dropOff:  3600  dropOffRate:  60.0%
  checkout        1200 users  conversionRate:  50.0%  dropOff:  1200  dropOffRate:  50.0%
  purchase         900 users  conversionRate:  75.0%  dropOff:   300  dropOffRate:  25.0%

  Overall conversion (visit -> purchase): 9.0%
  Step with the biggest leak: add_to_cart (dropOffRate: 60.0%)

funnelAnalysis() confirms, with an explicit rule in code (s.dropOffRate > worst.dropOffRate), what lesson 3 found by hand: the step with the biggest leak is add_to_cart, with a dropOffRate of 60%. Look at all four columns together, side by side, and notice the full pattern:

  • view_product loses 4,000 users in absolute numbers (the table's biggest drop), but its dropOffRate is only 40% — it's the funnel's second-best step, proportionally.
  • add_to_cart loses 3,600 users in absolute numbers (fewer than view_product), but its dropOffRate is 60% — the worst of the four. It's, literally, the strainer's proportionally biggest hole.
  • checkout loses only 1,200 users, with a dropOffRate of 50% — second worst.
  • purchase is, by far, the healthiest step: it loses only 300 users, a dropOffRate of barely 25%.

If a Mercado team had to pick a single step of checkout to invest next quarter's engineering and design in, this table says, unambiguously, which one: add_to_cart. It's not the step with the most traffic (that's visit), nor the step with the biggest absolute drop (that's view_product) — it's the step where, proportionally, the largest fraction of the people who arrived is lost. That's exactly the highest-potential-impact lever, and lesson 8's project will quantify how much fixing that specific step is worth in additional purchases.

dropOffRate is simply 1 - conversionRate: why the function calculates both

Notice that add_to_cart's dropOffRate is 60% and its conversionRate is 40% — they add up to exactly 100%, because they're the same information seen from two angles: the fraction that did continue (conversionRate) and the fraction that didn't (dropOffRate). So why does funnelAnalysis() calculate both numbers instead of just one? Because each one better answers a different question: conversionRate is the one you use to calculate overall conversion (lesson 4, multiplying survivals), and dropOffRate is the one you use to prioritize between steps (this lesson, finding the maximum). Having both, explicitly calculated and named in the object the function returns, avoids the mistake of having to mentally subtract 1 - x every time you want to compare steps — an extra step of mental arithmetic is, in practice, one more place someone gets it wrong under pressure in a meeting.

Common mistakes

Looking only at overall conversion and not at the leaking step. What happens: a team reports "our checkout conversion is 9%" quarter after quarter, uses it as the only tracking number, and never breaks down where, within the funnel, that loss is concentrated — so when the time comes to decide where to invest, there's no data to decide with, only intuition. Why it happens: overall conversion is a single number, easy to put on a slide and compare month to month; breaking it down by step requires a full table, which feels like "more work" for a meeting that just wants to see if the number went up or down. How to spot it: if a product's conversion report has a single number (overall conversion) and no per-step breakdown, the team may know that something got worse, but not where to act — it's a guardrail, not a diagnostic tool. How to fix it: overall conversion (lesson 4) and the per-step breakdown with dropOffRate (this lesson) aren't alternatives, they're complementary — the first tells you whether the whole funnel is improving or worsening; the second tells you which specific step to act on to move it. Always report both together.

Choosing the worst step by looking at dropOff (the absolute number) instead of dropOffRate (the proportion). What happens: someone reads funnelAnalysis()'s table, sees that view_product has the highest dropOff (4,000, more than add_to_cart's 3,600), and concludes that's the step to prioritize — reversing the correct priority order you already saw in this same lesson. Why it happens: dropOff is expressed in the same unit as users (people), which is easier to picture intuitively ("we lost 4,000 people") than an abstract percentage; the brain prefers concrete numbers over proportions. How to spot it: if the justification for "why we're prioritizing this step" cites a number of lost users instead of a leak percentage, check whether dropOff is being compared instead of dropOffRate. How to fix it: funnelAnalysis()'s hard rule is clear and admits no ambiguity: the worst step is decided with dropOffRate (s.dropOffRate > worst.dropOffRate), never with dropOff. The only case where dropOff (the absolute number) matters more is when calculating the real-user impact of fixing a step — but that's lesson 8's model, a different step that comes after deciding which step to prioritize.

Exercises

Exercise 1 — Find the worst step by hand. A funnel has this data: signup: 4000, verify_email: 3600, set_password: 1800, first_login: 1500. Calculate each step's dropOffRate (except signup) and say which is the worst.

See solution

verify_email: dropOff = 4000 - 3600 = 400, dropOffRate = 400/4000 = 10%. set_password: dropOff = 3600 - 1800 = 1800, dropOffRate = 1800/3600 = 50%. first_login: dropOff = 1800 - 1500 = 300, dropOffRate = 300/1800 ≈ 16.7%. The worst step is set_password, with a dropOffRate of 50% — half the people who verified their email never make it to setting a password. Notice that, in absolute numbers, set_password also has the highest dropOff (1,800) in this example — but that's a coincidence of this specific data, not a general rule; in Mercado's funnel, the worst step by rate (add_to_cart, a 3,600 drop) also didn't have the biggest absolute drop (that was view_product, with 4,000).

Exercise 2 — Design a funnel where the worst step by dropOff and by dropOffRate are the same. Invent four user counts for a three-step funnel where the step with the highest dropOff (absolute number) also has the highest dropOffRate (proportion) — that is, a case where both criteria agree instead of contradicting each other like in Mercado's funnel.

See solution

A valid example: [{name:'a', users: 1000}, {name:'b', users: 800}, {name:'c', users: 200}]. Step b: dropOff = 200, dropOffRate = 200/1000 = 20%. Step c: dropOff = 600, dropOffRate = 600/800 = 75%. Here c wins on both metrics: it has the highest dropOff (600 versus 200) and the highest dropOffRate (75% versus 20%). This can happen when the step with the worst rate also occurs at a point in the funnel with still-considerable user volume (not too deep into the funnel, where absolute numbers are already small by nature). Mercado's case, where the two criteria disagree, is more instructive for the module, but both cases are possible in real data — that's why funnelAnalysis() never assumes which one will line up, and always uses dropOffRate as the decision criterion.

Exercise 3 — Spot the error in a product decision. A product manager says: "We're going to spend the sprint optimizing view_product, because that's where we lose the most users overall (4,000, according to the dashboard)." Using this lesson's Mercado funnel data, explain why that decision, as justified, doesn't follow funnelAnalysis()'s rule.

See solution

The decision uses dropOff (4,000, the highest absolute number) as the criterion, when funnelAnalysis() uses dropOffRate to decide the worst step. With dropOffRate as the criterion, view_product has a 40% leak — the second best of the four steps, not the worst. The step that actually has the biggest proportional lever is add_to_cart, with a 60% leak. This doesn't mean investing in view_product is necessarily a bad idea in some broader business sense (there could be other valid reasons, like the volume of traffic passing through it) — but the specific justification the PM gave ("it's where we lose the most users") describes dropOff, not dropOffRate, and is therefore not the correct reason to prioritize that step over add_to_cart under this lesson's logic.

Summary and next step

funnelAnalysis() brings together the conversion rate (lesson 3), overall conversion (lesson 4), and a new piece —dropOff and dropOffRate per step— into a single function that also automatically identifies the step with the highest proportional leak. On Mercado's checkout, that step is add_to_cart, with a dropOffRate of 60%: the strainer's biggest hole, not the level with the most water on top.

Before moving on you should be able to: calculate a step's dropOff and dropOffRate by hand; and explain, with Mercado's example, why the step with the highest absolute dropOff isn't necessarily the step with the highest dropOffRate.

So far, every rate you calculated was local — one step against the immediately previous one (micro-conversion). Lesson 6 introduces its counterpart: macro-conversion, the rate of a wider stretch of the funnel (for example, from view_product to purchase, skipping intermediate steps), and when each one is the right question to ask.

Resources