A response arriving in 800 milliseconds can feel instant or sluggish depending entirely on what happened during those 800 milliseconds. Perceived performance is not a consolation prize for teams that cannot optimise the real thing - it is a distinct discipline, and for operations with an irreducible floor, like generating text with a language model, it is the only lever left.
01Show progress, not absence
A blank screen provides no information about whether anything is happening, how long it might take, or whether something has broken. Users interpret silence as failure and start clicking again, which frequently makes things worse by issuing duplicate requests.
Skeleton screens work better than spinners for content because they communicate structure - the user sees the shape of what is arriving and can begin orienting before the data lands. A spinner says something is happening; a skeleton says specifically what is coming.
- Show layout structure while content loads, not a bare spinner.
- Give any operation over about a second explicit progress feedback.
- Disable the trigger during submission to prevent duplicates.
- Never leave a screen visually identical before and after an action.
02Streaming changes the perceived floor
For generated content, streaming tokens as they are produced transforms the experience. Waiting eight seconds for a complete answer feels broken. Seeing text begin within 300 milliseconds and continue steadily feels responsive, even though total time is identical or slightly worse.
The metric that matters shifts from total duration to time-to-first-token. Optimise for that specifically: start streaming before the whole pipeline completes, send the first meaningful content as early as possible, and avoid buffering layers that hold output until it is complete. A proxy or middleware that buffers the response silently destroys the entire benefit.
03Optimistic updates, applied carefully
When an action will almost certainly succeed - liking a post, marking a task complete, sending a message - updating the interface immediately and reconciling with the server afterwards makes the application feel instantaneous, because from the user's perspective it is.
The discipline is handling failure honestly. Rolling back silently confuses people who saw their action succeed. Show a clear, non-destructive error, preserve whatever they entered, and offer a retry. And reserve the pattern for high-probability operations: applying it to a payment or an irreversible action creates a much worse experience than a brief wait would have.
| Operation | Technique | Reason |
|---|---|---|
| Toggle, like, favourite | Optimistic update | Near-certain success; trivial rollback |
| Form submission | Progress + disabled control | Prevents duplicates; sets expectation |
| Generated content | Stream tokens | First token dominates perception |
| Navigation | Prefetch on intent | Feels instant when it lands |
| Payment | Explicit progress, no optimism | Rollback would be alarming |
| Long batch job | Background with notification | Do not hold the user hostage |
04Prefetch on intent
The fastest request is the one already in flight when the user commits to it. Hovering a link, focusing a control, or beginning to scroll toward a section are all signals of intent that arrive slightly before the action, and that gap is enough to hide meaningful latency.
Be proportionate. Prefetching everything visible wastes bandwidth and can cost more than it saves on mobile connections, and it can trigger side effects if the prefetched endpoint is not genuinely idempotent. Prefetch the most likely next step, not every possibility.
05Do not fake it
Perceived performance techniques manage expectation honestly; they do not deceive. Progress bars that advance on a timer rather than actual progress, or artificial delays added because instant results feel untrustworthy, both damage credibility when users notice - and they do notice, because the fake progress bar always stalls at the same place.
The honest version is better anyway: show real progress where you have it, be clear when you cannot estimate, and communicate failure promptly rather than leaving a spinner running indefinitely. Users forgive slowness far more readily than they forgive being misled about it.
Topics
Priya Iyer
Staff Engineer · SyncTrix
Writes about the engineering decisions behind production systems - architecture, delivery and the trade-offs that only show up at scale.
Building something like this?
SyncTrix engineers AI, SaaS, platform and cloud systems for enterprises and high-growth teams. Tell us what you're shipping and we'll scope it with you.