For a list fed by a server request, why do a first-run empty collection and a filtered zero-results view need different branches?
answer
- same payload, different cause
- look at the request, not the length
- create it versus widen it
- a caught failure is not empty
- each branch needs its own action
basics
~20 sThe payload is the same empty collection, but the cause and the next step differ. Nothing created yet needs the action that creates the first item; a filter that excluded everything needs widening or clearing. One shared message misleads one audience.
solid answer
~50 sBoth branches render from a successful response with no items, so the component cannot tell them apart from the payload alone — it has to look at the request it sent. If the request carried no narrowing criteria, the collection itself is empty: the user has nothing yet, and the branch should explain what this surface will hold and offer the action that creates the first entry. If the request carried a filter, a search term, or a page beyond the end, the collection may be full and only this view is empty: the branch should echo what was searched and offer to relax or clear it. A third case hides here too — a failure that was caught and turned into an empty collection, which then tells the user their data does not exist. Keep failures in the error branch.
go deeper
Remember that an empty list has more than one cause, and that the message has to match the cause. Check whether your screen can tell a brand-new user apart from one whose search matched nothing.
Explain that the branch is chosen from the request parameters rather than the payload, and name the third impostor: a caught failure rendered as empty. Say what action each branch should carry.
Demonstrate the production view: empty-branch rates as a product signal, failures never laundered into empty states, announcements for assistive technology, and reserved layout so the swap does not move the page.
Decide how much of this is a shared component with slots versus per-surface copy owned with writers, and what the review gate is. Over-standardised copy is how a filter message reaches a first-run screen.
## One payload, two different sentences A successful response that carries no items is not one state. It is at least two, and they call for opposite next steps: | Branch | What produced it | What the branch should offer | Copy that gets it wrong | |---|---|---|---| | Empty collection | The request asked for everything and there is nothing | An explanation of what this surface holds, plus the action that creates the first entry | "No results found — try different filters" for a user with no filters | | Zero results | The request narrowed by search text, filters, a date range, or a page past the end | The criteria that were applied, and a way to relax or clear them | "Get started by creating your first item" when the account has thousands | | Failure rendered as empty | A rejected request whose handler substituted an empty collection | Nothing — this belongs in the error branch with a retry | Any empty-state copy at all, because it asserts something untrue | The payload cannot distinguish these. **The request can**: the component knows whether it sent narrowing criteria, so the branch is chosen from the request parameters, not from the length of the response. ## Why it matters more than it looks - **The wrong branch blocks the user.** A new account shown "try clearing your filters" has no filters to clear, and the one thing it needed — the create action — is absent from the screen. - **The wrong branch reads as a bug.** A user who has just searched for a misspelled term and is told to "create your first item" concludes their data is gone. - **Emptiness is a product signal.** The first-run branch is often a user's earliest real interaction with a surface, and the zero-results branch is a measurable sign that a filter or a search is too strict. - **Both branches still have to hold layout.** They replace content, so a one-line message where a table used to be collapses the region and moves everything under it. ## Telling them apart in practice 1. Decide what "narrowed" means for this surface: a non-empty search string, any active filter, a non-default date range, a page index greater than the first. 2. If nothing is narrowed and the response is empty, render the first-run branch. 3. If something is narrowed, render the zero-results branch and echo the narrowing back, so the user can see what excluded their data. 4. If the request failed, render the error branch — never either empty branch. A frequent refinement is a **fourth** case: narrowing is active, the response is empty, *and* the underlying collection is known to be empty too — for instance because a previous unfiltered read returned nothing. Then the first-run branch is the more useful one, because clearing the filter would not help either. This needs information the current request does not carry, so treat it as an enhancement rather than the default. ## The failure impostor The most damaging version of this bug is a request handler that swallows a rejection and returns an empty collection so the render code has fewer branches to write. The screen then states, in confident product copy, that the user has no data. It also destroys the retry affordance: there is no failure on screen to retry, so the user's only recourse is reloading. Rejections belong in the error branch; the fact that both render "no rows" is a coincidence of the payload, not a shared meaning. ## Related branches that are not this one A **partial** result — some items plus a note that one source failed — is a distinct branch again, and usually renders the data it did get with a warning rather than either empty state. A **not-found** single record is closer to an empty state than a failure in user terms, even when the transport reported a missing resource, and it typically wants navigation back to a list rather than a retry button. ## Accessibility and layout notes - These branches replace content, so announce the change in a live region rather than relying on the user noticing the swap; a screen-reader user who submitted a search otherwise hears silence. - Keep the message inside the same container the rows occupied, with enough reserved height that surrounding controls do not move. - Put the action in the branch, not somewhere else on the page: the whole point of the branch is that the user's next step is unambiguous. ## How this shows up in review - One shared empty component with one string, used for every surface and every cause. - Copy that mentions filters on a surface that has none. - An empty branch reachable from a failed request. - An empty branch with no action, leaving a dead end.
- How does the component decide which of the two empty branches to render?From the request, not the response. If the request carried a search term, an active filter, a narrowed range, or a page past the end, the view is narrowed and the zero-results branch applies; if it asked for everything, the collection itself is empty. The payload is identical in both cases, so the length of the list cannot decide it.
- Why is turning a failed request into an empty collection worse than letting it fail?It publishes a false claim — that the user has no data — in confident product copy, and it removes the retry affordance, because there is no failure on screen to retry. It also hides the failure from anyone watching error rates, since the request now looks successful and merely unproductive.
- Does a request for a page past the end of a collection belong in the zero-results branch?Usually yes, and it deserves its own wording: the collection is not empty and the criteria are not too strict — the user is simply past the last page, often after a deep link or a deletion. The useful offer is a jump back to the first page rather than clearing filters.
A library shelf with no books on it and a catalogue search that matched nothing are both "nothing to show", but one needs a delivery and the other needs a different search term.
saying these in an interview costs you the question
- Uses one empty message for every surface and every cause
- Tells a brand-new user to clear filters they never set
- Decides the branch from the response length instead of the request
- Turns a rejected request into an empty collection
- Renders an empty branch with no next action
- Lets the message collapse the region so the page jumps