skip to content

As a lead, how would you set one convention for loading, error and empty states across many screens built by several teams?

level: principalimportance: nice to knowfreq 38%

answer

  1. standardise obligations, not pixels
  2. shells with slots, not mandates
  3. granularity is per screen
  4. one review question catches most
  5. instrument each state

basics

~20 s

Standardise the vocabulary and the obligations: the named states every data-backed surface must handle, the copy pattern and action each branch carries, the escalation policy. Leave placeholder shape and fallback granularity to the team that knows the screen.

solid answer

~50 s

Split the problem into what must be identical and what must not. Identical: the set of states a data-backed surface is required to handle, the rule that a refresh never blanks content a user is reading, the copy pattern and the single next action each branch carries, the escalation policy for what may take down a region, and the accessibility obligations when a region swaps. Deliberately local: the shape and size of each placeholder, which must match its own content, and where boundaries sit, which must follow each screen's slow regions. Ship it as shared shells with slots, so teams compose rather than fork, and add a review gate: a surface that renders data cannot ship with a pending branch and nothing else. Then instrument it — time spent in each state, error-branch impressions, empty-branch rate — so the convention is judged by evidence rather than by taste.

go deeper

for a junior

Follow the shared shells and copy patterns rather than inventing a message per screen, and ask which branches a surface is expected to have before you build it.

for a middle

Be able to say which parts of such a convention are universal — the states, the no-blanking rule, the copy pattern — and which have to be decided per screen because they depend on layout and latency.

for a senior

Bring the enforcement view: shells with slots so the convention is the easy path, one review question that catches most omissions, and instrumentation so the argument is about evidence.

for a principal

Own the split between coherence and local judgment, the escalation depth, the retry policy that protects the backend, and a sanctioned exception path. State the tradeoff rather than pretending one rule fits every surface.

## What a convention here is actually for Without one, the same product shows five different sentences for an empty list, three visual languages for pendingness, and one screen where a background refresh blanks a table the user was reading. These states are where a product feels either solid or broken, and they are written by whoever happened to build each screen, usually last, usually in a hurry. The lead's job is to make the right thing the cheap thing. ## Standardise the obligations, not the pixels | Standardise | Leave to the team | Why | |---|---|---| | The named states every data-backed surface handles | Which states a given surface actually reaches | Some surfaces genuinely have no idle or no empty | | "A refresh never replaces content the user is reading" | How progress is signalled on that surface | The rule is universal, the affordance is contextual | | Copy pattern: one sentence of cause, one action | The words | Writers own voice; the pattern prevents dead ends | | The escalation policy: what may take down a region | Where the boundaries sit on this screen | Granularity must follow each screen's slow parts | | Announcement and focus behaviour when a region swaps | The exact announcement text | Accessibility is a floor, not a preference | | Bounded retry behaviour for reads | Whether this surface retries at all | Client retry storms are an app-wide risk | | That a placeholder reserves the content's space | The placeholder's shape and size | A shared fixed size is wrong for every surface | The two rows that teams most often get backwards are the last two. A single shared indicator used everywhere guarantees that half the surfaces reserve the wrong amount of space; a single global fallback granularity either withholds fast content or speckles the screen. Both are decisions that need the screen in front of you. ## Make it the easy path, not a mandate 1. Ship shells with slots — a region wrapper that takes the illustration, the sentence and the action — so using the convention is less work than writing a branch from scratch. Anything a team must fork to customise will be forked and then diverge. 2. Provide the state model itself, so surfaces derive one named status rather than assembling flags. Half the defects in this area are contradictory flags, and a shared model removes them by construction. 3. Publish the thresholds — the delay before a placeholder appears, the minimum time it stays — as defaults teams can override with a reason, not as constants baked into a component. 4. Put the failure taxonomy in the shared layer: offline, refused, unauthorised, missing, malformed. Teams should choose among named cases, not invent strings. ## Enforce at the cheapest point - **Review gate:** a surface that renders server data does not ship with only a pending branch and a data branch. This is one question in review and it catches the majority of defects. - **Static checks** where the shape allows it: a render path with a pending branch and no failure branch, a handler that substitutes an empty collection for a rejection, a value cleared at request start. - **A states page** in the component gallery where every surface's branches can be viewed directly, so a missing branch is visible rather than theoretical. - **Tests as the receipt:** each surface asserts its own branches. That assertion belongs with the testing conventions, but the requirement to have it belongs to this policy. ## Instrument it, then argue from evidence - Time users spend in each state per surface: a surface whose pending branch is never visible does not need an elaborate one; one where it dominates has a data problem, not a design problem. - Error-branch impressions per surface, split by failure cause, and how many retries succeed — a retry that never succeeds is a false promise. - Empty-branch rate split into first-run and zero-results: a high zero-results rate is a product signal about filters and search, not a UI defect. - Layout movement around these swaps, since this is where most of it comes from. ## What to decide explicitly rather than let drift - Whether polling continues while a surface is hidden, and what the app does on reconnect. - Whether a failed background refresh may ever replace good content — the answer should be no. - How deep escalation may go, so a decorative failure can never blank a working page. - Who owns the words. Copy written by engineers under deadline is the reason empty states tell new users to clear filters. ## The tradeoff to be honest about Every rule here trades local optimality for coherence. A team with a genuinely unusual surface will sometimes be right to break the convention, so give them a sanctioned exit: an explicit override with a recorded reason, reviewed. A convention with no exit is one that gets bypassed silently, and then you have neither coherence nor local judgment.

  • Which parts of this should deliberately not be standardised?
    Placeholder shape and size, because each must approximate its own content, and fallback granularity, because it has to follow each screen's slow regions. A single shared indicator or one global boundary rule guarantees wrong layout reservation or fast content waiting on slow content somewhere in the app.
  • What is the cheapest enforcement mechanism you would put in place first?
    A single review question: does this surface render something distinct for pending, failed and successful-but-empty? It costs nothing, catches the most common defect, and is easy to apply consistently. Static checks and a gallery page showing every surface's branches come next, because they make omissions visible without a reviewer.
  • How would you know the convention is working?
    By measuring rather than reviewing taste: time spent in each state per surface, error-branch impressions by cause, how often retries succeed, empty-branch rate split into first-run and zero-results, and layout movement around the swaps. Those numbers also tell you which surfaces need data work instead of design work.
  • Why provide a sanctioned exception path?
    Because some surfaces genuinely differ, and a convention with no exit gets bypassed quietly — leaving you without coherence or a record of the decision. An explicit override with a stated reason, reviewed once, keeps the divergence visible and reversible.

saying these in an interview costs you the question

  • Mandates one indicator and one fixed placeholder size everywhere
  • Sets a single fallback granularity for the whole app
  • Writes the convention as a document with no shared components
  • Leaves failure copy to whoever builds the screen
  • Measures nothing and argues about these states by taste
  • Allows no exception path, so teams bypass the convention silently