/
Performance & Feedback
The seconds between a tap and a response decide whether users trust your product. Match the right pattern to the wait, the scope, and the reversibility of the action.
what would you do?
Your import flow takes 15-40 seconds depending on file size. Right now it shows a single spinner with no other feedback. What should you build instead?
Support tickets show users refreshing the page mid-import, assuming it's frozen, and re-uploading the same file. The wait is well past the point where a spinner reads as trustworthy, and duration varies enough that you can't hardcode a countdown.
A
Keep the spinner but add a bigger, more noticeable animation
B
Switch to determinate progress with step labels ("Validating rows... Step 2 of 4") and a way to background the task
C
Replace the spinner with a full skeleton screen of the results table
D
Add a static "Please don't close this window" warning above the spinner
The Problem
Users don't experience "performance" as a backend metric. They experience it as a moment of uncertainty: did my tap register, is this broken, should I click again? NN/G's timing thresholds still map cleanly to product work: around 0.1 seconds feels instantaneous, 1 second preserves flow, and 10 seconds is roughly where attention starts drifting [1].
That uncertainty has measurable commercial consequences. Baymard's mobile research found that slow response times on mobile sites were common, severe, and caused abandonment during testing, and Reforge notes that load time can be a major driver of bounce rate on acquisition-sensitive surfaces [2].
There's a subtler failure too: a product can look ready before it's actually usable. When buttons are visible but not yet interactive, people assume the page is broken and leave, or stack clicks and create duplicate actions. Loading states are the most routine way products either pass or fail the basic test of answering a user's action clearly [3].
The Three-Question Framework
How long will this probably take?
What surface is loading: a small control, a module, or a whole page?
Can the UI safely assume success and reconcile later?
The practical rule is to respond immediately, then escalate the amount of feedback only as needed. For sub-second waits, a pressed state or optimistic response is usually enough. For short isolated waits, use a spinner. For full-screen content loads, use a skeleton that reveals structure. For long or process-heavy waits, switch to determinate progress with steps and time estimates [4].
Skeletons are not "better spinners." NN/G recommends them specifically for full-page loads, not for uploads, conversions, or other process tasks. Their value is that they preview layout, reduce uncertainty, and create the impression of gradual progress [5].
Determinate versus indeterminate is the foundational split. Apple's HIG frames it simply: use determinate indicators when you can estimate duration, and indeterminate ones when you can't. Material and Android converge on the same logic in their own component language [6].
Optimistic UI removes the wait entirely for simple, reversible, high-success actions, responding before the network round trip completes and rolling back on failure. Smashing Magazine's constraint is that failures should surface within about 2 seconds so the interface doesn't effectively lie about success [7].
Real Product Case Studies
Full Skeleton Screen on App Launch
Previewing the home screen's layout before restaurant data resolves

Uber Eats
The design decision
On launch, the home screen renders its full structure immediately, search bar, category circles, and stacked restaurant cards, all filled with gray placeholder blocks in the exact size and position the real content will take. Nothing waits behind a spinner; the whole page appears at once, just unresolved.

Why it works
Because location, delivery address, and nearby restaurants all depend on a slower personalization query, showing the finished layout in outline form immediately tells users the app is alive and exactly what kind of screen they're on. NN/G's research on skeleton screens found this reduces the abruptness of a blank-to-dense transition and creates the impression of a shorter wait [5].
Descriptive Indeterminate State for AI Processing
Naming an unpredictable process instead of leaving it silent
Step-Based Progress for a Personalization Flow
Breaking a multi-second setup process into visible, sequential stages
Pattern Fit by Use Case
Use Case | Recommended Pattern | Avoid |
|---|---|---|
Feed or home timeline | Skeleton screen, progressive reveal | Full-screen spinner |
Analytics dashboard | Section-level skeletons per card | One global spinner blocking everything |
Modal opened from current page | Small spinner inside the modal body | Full-page skeleton for a small overlay |
Form submit under a few seconds | Button loading state, disable repeat submit | Static "Please wait" warning text |
Upload, import, export, conversion | Determinate progress, steps, or queue estimate | Skeleton screens |
Like, save, wishlist, archive | Optimistic UI with rollback | Spinner on a near-certain, low-risk action |
Mistakes That Kill Success
avoid this
Skeletons for Process Tasks
Using a skeleton for an upload, conversion, or payment check. NN/G is explicit that skeletons are for full-page loads, not process work, they imply "page structure is arriving" when what's actually happening is "a job is running" [5].
The fix
Swap to a determinate progress bar, step indicator, or queue estimate for anything that's genuinely a process, not a page render.
avoid this
A Page That Looks Ready Before It Is
Buttons and content render before handlers are actually wired up. Users tap, nothing happens, they assume the page is broken and either leave or stack duplicate clicks [3].
The fix
Keep controls visibly disabled until they're truly interactive, or use progressive reveal that makes the hydration state explicit.
avoid this
Static Text on Long Waits
A bare "Loading..." label or an infinite spinner past 10-20 seconds gives no signal that anything is actually progressing, so users start refreshing or resubmitting[4].
The fix
Move to determinate progress past 10 seconds, and refresh the message once a wait passes 20 seconds so it still reads as alive.
Metrics That Matter
Largest Contentful Paint (LCP)
Time until the main content element is visible. Target: under 2.5 seconds at the 75th percentile, segmented by mobile and desktop.
Why it matters: LCP is the true-performance baseline that any loading pattern is layered on top of, it measures loading, not perceived polish [9].
Interaction to Next Paint (INP)
How quickly the product reacts once a user taps or clicks. Target: 200 milliseconds or less.
Duplicate-Click Rate
Percentage of submissions where a user pressed the same control more than once. Formula: duplicate clicks ÷ total submissions × 100
The Strategic Opportunity
3x
How much longer people are willing to wait when a moving progress indicator is shown, compared with no indicator at all. Good feedback doesn't fake speed, it keeps people engaged long enough for real value to arrive.
Feedback Extends Patience
NN/G cites evidence that moving progress bars increase satisfaction and can make people willing to wait roughly three times longer than when no indicator is shown at all [4].
Slow Response Times Cause Abandonment
Baymard's mobile research found slow response times on mobile sites were common, severe, and directly caused abandonment during usability testing [2].
Small Speed Gains Move Conversion
NN/G notes that shaving even 0.1 second from response time can improve conversion, performance is interpreted as competence[1].
Prototyping Loading States Early Pays Off
Reforge frames speed as a driver of bounce rate and recommends prototyping error, empty, and loading states early, they're easy to miss in static design files [10].
Resources Worth Your Time
Loading Pattern Guidance
Optimistic UI & Perceived Performance