The Information Hierarchy of Waiting
Human perception of time is not linear. A 200-millisecond delay feels instantaneous; a 2-second delay feels like the interface broke. The threshold between "I didn't notice a delay" and "something is wrong" is surprisingly low — around 100ms for the feeling of immediate response, 1 second for the feeling of acknowledged input, and 10 seconds before users start considering abandoning the task. These thresholds are physiological, not cultural. They don't change across devices or demographics.
This means loading state design operates within a strict time-based hierarchy. Under 100ms: do nothing, the user doesn't perceive a delay. 100ms–1s: provide instant visual feedback that the action was received — a button press state, a cursor change, a micro-animation. 1s–3s: provide an acknowledgment with some indication that progress is happening. Over 3s: progress indication becomes mandatory; a bare spinner is the minimum acceptable signal, not the ideal one. Over 10s: time estimation becomes necessary — "this usually takes about 30 seconds" is dramatically better than an indefinite indicator.
The hierarchy matters because most teams design one loading state and apply it everywhere. A spinner that appears in 80ms communicates anxiety; the same spinner that appears after 1.5 seconds communicates acknowledgment. The state is identical; the communication is opposite. Loading state design requires asking not just "what visual do I show?" but "when does this visual appear, and what does its timing communicate to the user?"
Skeleton Screens vs Spinners
The choice between skeleton screens and spinners is not aesthetic — it is semantic. Each communicates a different thing about the state of the interface, and using the wrong one sends false information to the user.
A skeleton screen communicates: I know what this content will look like, I know where it will go, and I'm fetching the data to fill it. It is content-aware. It tells the user the shape of the incoming information, which dramatically reduces perceived wait time — the brain begins to process the layout before the content arrives, so the content appears to load faster even if the actual latency is identical.
A spinner communicates: something is happening, I don't know what, I don't know how long. It is content-agnostic. It gives the user no information about what is loading, when it will be done, or what they will see when it finishes. In contexts where you genuinely don't know the shape of the incoming content, this is appropriate. In contexts where you do know — which is most contexts — using a spinner is wasting an opportunity to reduce perceived latency.
The rule of thumb: use skeleton screens when you know the content structure ahead of the data. Use spinners for truly unknown async operations — API calls that might return anything, long-running processes with unpredictable outcomes. If you find yourself using spinners for well-structured content, it's usually a signal that the content structure wasn't considered in the loading state design pass.
Progressive Loading
Progressive loading is the practice of displaying available content immediately while fetching the rest, rather than withholding all content until everything is ready. It is one of the most effective perceived performance improvements available to product designers, and one of the most commonly skipped.
The principle is straightforward: prioritize above-the-fold content, render what you have as soon as you have it, and let below-the-fold content load asynchronously as the user scrolls. A news article that shows the headline and first paragraph while the images and sidebar load feels dramatically faster than an article that shows nothing until every element is ready — even if the total time to full load is identical.
Progressive loading requires coordinating across engineering and design. Designers need to explicitly define the loading priority order: what should appear first, second, third? Engineers need to implement the data fetching in a way that honors that priority — which often means breaking a single "get everything" API call into multiple prioritized requests. This is more complex, but the user experience payoff is significant enough that it should be the default approach for any content-heavy interface, not an optimization to apply after launch.
Optimistic UI
Optimistic UI is the practice of updating the interface immediately when the user takes an action, before the server confirms the action succeeded, and then rolling back if it didn't. It's the most aggressive perceived performance optimization available — it makes latency effectively invisible for the common success case.
When to use it: optimistic UI is appropriate when the action has a very high success rate (>99%), when the failure case has a clear and recoverable rollback UI, and when the action is reversible (or the stakes of the irreversible case are low). Liking a post, sending a message, reordering a list — these are good optimistic UI candidates. Wire transfers, account deletions, destructive operations — do not use optimistic UI for these. The failure case is too consequential and the user needs to know the action's actual status before proceeding.
The failure rollback design is often neglected. Most teams implement optimistic UI for the success path but don't put equivalent effort into the failure path. A good rollback shows the user exactly what was undone, explains why, and provides a clear path to retry. A bad rollback silently reverts to the previous state without explanation, leaving the user confused about what happened. Design both paths with equal care.
Error States Are Also Loading States
The loading state sequence is: loading → success or loading → error. Most interfaces design the success case and treat error as an afterthought. Error states are not afterthoughts — they are the honest completion of the loading state design. Every loading state you design should have a corresponding error state that you have also designed.
Good error states do three things: they tell the user what happened in plain language ("We couldn't load your orders"), they explain what the user can do ("Try again, or check your connection"), and they preserve any user input so the user doesn't have to start over after fixing the underlying problem. This last point is consistently the most neglected — nothing destroys user trust faster than losing form input to a network error and being shown a generic "something went wrong" message with no recovery path.
The visual design of error states should be distinct from loading states but within the same visual language as the rest of the interface. An error state that looks like a system error page, not a designed product screen, communicates that this was unexpected — which communicates that the engineering team didn't plan for it — which communicates low product quality. Error states that are clearly designed tell the user: this was anticipated, the team built for it, you're in good hands.
Writing Loading Copy
Loading copy is often the last thing considered and the first thing users read. It is also the only communicative element visible during the loading state — when the content isn't there yet, the copy is all the user has to understand what's happening.
The rule for loading copy: be specific about what is loading, not generic about the state of loading. "Fetching your orders" is better than "Loading." "Generating report — this usually takes about 30 seconds" is dramatically better than "Please wait." Time estimates reduce anxiety even when they're approximate — users tolerate known waits far better than unknown ones. If you don't know how long something will take, "this might take a moment" is better than silence, but a real time estimate is better still.
Progressive copy is a technique worth adopting for long operations: change the loading message at intervals to reflect actual progress through the operation. "Analyzing your data... Generating visualizations... Formatting your report..." This serves two purposes: it reassures the user that something is actually happening (the persistent question during any long wait), and it provides something new to read, which makes the wait feel shorter. The copy doesn't need to be technically accurate — it needs to be emotionally accurate. Users don't need to know about your microservice architecture; they need to know the system is working on their behalf.