Tags funnelanalyticsMixpanelFirebase Analyticsinstrumentation
All posts

funnel

A funnel with every screen logged, and no idea where users left

Screen tracking was firing, yet the funnel question stayed unanswerable. The data landed where I could not query it, and one step I added would read 100% forever.

August 24, 2026 4 min

I wanted to know which step users drop out at. Instrumentation was already there: a screen_view on every navigation, plus events on the meaningful actions.

Then I sat down to answer the question and couldn’t. Not because nothing was recorded, but because the place things were recorded and the place I could ask questions were different places.

The funnel data was in a room I could not enter

My funnel report could not see behavior, only artifacts. Every navigation writes screen_view to Firebase Analytics and Mixpanel. Both are dashboards you look at with your eyes. Neither is reachable from the analysis scripts I actually run.

So the funnel report had been built a different way: read the database directly and count documents. Created a dream? There’s a document, so that step is known. Added a task? Another document.

The problem is any behavior that creates no document. Someone who opens a screen, looks, and leaves writes nothing at all. From the database, all I can say is “this user has no dream.”

I could not distinguish never having reached the dream-creation screen from reaching it and closing it. Those two have opposite fixes: one is a routing problem, the other is a copy or layout problem. I had been treating them as one number for months.

Ten of thirty-four declared events had never fired

Before changing anything I counted what was already there. Event names are declared as a union type, so I extracted the declared list and the actual call sites and diffed them.

declared        34
actually called 25
never called    10

Ten names existed in the type and nowhere in the code, leftovers from features whose buttons were removed while the event declaration stayed.

This matters because searching a dashboard for one of those names returns zero with no error. Zero reads as “nobody uses this feature.” It actually meant “this was never measured.”

The fix was to write breadcrumbs where I can read them

Two options. Wire up an export API from the dashboard tools, or write a minimum to the database I already query.

I took the second. The only thing I need is how far each person got.

signup          signed up
dream_modal     saw the dream-creation screen
dream_created   created a dream
timeblock       reached the day tab
todo_added      added one task
todo_completed  completed one task

Six steps, first-arrival timestamp only, written once per step. Six writes per user for their entire lifetime, so cost is not a consideration. Counts of repeat actions stay in the existing tools; here I only care about order and reach.

The second line is the whole point. Recording that the screen was seen splits the ambiguous group in two: saw-and-didn’t-create is a copy problem, never-saw is a routing problem.

I nearly shipped a metric that lies

The first draft had eight steps. Two more at the front:

app_open    opened the app
hero_done   finished the intro slides
signup      signed up

You want the funnel to start at the beginning.

Writing the code, it turned out neither could be recorded. The intro screens run before authentication, so there is no user document to write to. Same for the moment the app opens.

So I cheated: I stamped app_open inside the signup handler, reasoning that the account’s first moment is as early as I can get.

Running it showed the problem. app_open and signup carry the same timestamp, so that step reports 100% pass, permanently.

Anyone reading the report concludes “everyone who opens the app signs up.” The truth is that segment was never measured at all.

That is worse than missing data. Missing is visibly missing; 100% looks like a measurement.

I deleted both steps. The report now starts at signup and states in its own output that the segment before it cannot be measured here.

The biggest gap was one I had never looked at

The new instrumentation hasn’t reached users yet, so I reconstructed the flow from document timestamps for the 43 people who signed up in the last 30 days.

Where they stoppedCount
No dream19
Dream but zero tasks18
Tasks but zero completed3
Completed something3

The middle row is the one I could not see before. Of the 24 who created a dream, 18 never added a single task. My existing report went straight from “first dream” to “first completion,” so that entire segment was invisible.

One more: 31 of 43 finished all activity within two minutes by document timestamps. Eight seconds. Forty seconds. Fifty-one seconds. That is not someone getting stuck partway; that is a look-around-and-leave. Same churn number, different fix.

Two things I would tell myself earlier

Recording data and being able to answer a question are separate achievements. If the data accumulates somewhere you cannot reach, the instrumentation is decorative. Decide where you will read it before you decide what to log.

And a step that always reports 100% is not a metric. It means you did not measure that segment, and putting it in a report misleads whoever reads it. Dropping the row and stating the limitation is the honest version.

Scope

Measured 2026-08-24 on an Expo SDK 56 / React Native 0.85 app using Firebase Analytics and Mixpanel together. The 43-user sample is small enough that I used it for ordering, not for rates.

The new breadcrumbs shipped today and have no data yet, so whether this actually separates the two kinds of drop-off is still unverified. I will revisit when there are numbers.

Previous One line changed and git recorded 295