Deceptive design pattern

Dishonest progress indicators and the wait they are hiding

A dishonest progress indicator is one whose displayed state is not derived from the work being done — a bar that animates on a timer, one that jumps to 90% and stops, or a control whose label does not match its effect. It matters because a progress indicator’s entire job is to let someone decide whether to keep waiting, and an indicator that reports a number it did not measure removes the information that decision needs.

What it is

A progress indicator answers two questions: is this still working, and roughly how much longer. A determinate indicator — a percentage, a bar, “3 of 12 files” — answers both. An indeterminate one, a spinner or a looping bar, answers only the first, honestly, by making no claim about the second.

The failure this page is about happens when the visible progress stops being connected to the underlying work. That covers the bar driven by a fixed animation regardless of what the server is doing; the one that climbs to 90% in two seconds and then sits there for a minute because the first 90% was a guess and the remaining 10% is the actual request; the one that resets or slides backwards when a retry happens; and the “estimated time remaining” that oscillates between four seconds and eleven minutes because it is extrapolating from the last sample rather than from a trend.

It also covers the control problem: buttons that claim to influence a wait and do something else. A Cancel that stops the animation but not the upload, a Retry that queues a second identical request, a Pause that only pauses the display. These are the same failure as a mislabelled progress bar, because the user has no way to observe the underlying process except through the interface that is misreporting it.

The Progress Paradox level makes that literal. A bar fills toward 100% and offers two controls. Speed Up halves the rate. Pause doubles it. Nothing about the display reveals this; getting through means abandoning what the labels say and acting on what the bar actually does. It is a small joke about a real category of bug — a control whose label describes an intention rather than an effect — and about how quickly people stop trusting an indicator once it has misled them once.

Why it works on people

Progress indicators are worth getting right because people genuinely want them. Brad Myers established this at CHI in 1985, in what remains the standard citation: across a formal experiment, participants showed a clear preference for having a percent-done indicator during long operations. Worth noting for accuracy is that Myers also attempted to replicate an earlier finding about constant versus variable response time and did not reach statistical significance — the durable result from that paper is the preference for indicators, not a general law about response-time variance.

How long a wait can go unexplained is well characterised. Jakob Nielsen’s summary of the response-time literature, drawing on Miller’s 1968 work and on Card, Robertson and Mackinlay, gives three limits: 0.1 second is “the limit for having the user feel that the system is reacting instantaneously”; 1.0 second is “the limit for the user’s flow of thought to stay uninterrupted”; and 10 seconds is “the limit for keeping the user’s attention focused on the dialogue”. Past ten seconds, an indicator has to carry real information, because the user is deciding whether to stay.

Perceived duration is not the same as measured duration, and this is where the temptation to lie comes in. Harrison, Amento, Kuznetsov and Bell tested nine progress-bar functions at UIST 2007, holding actual duration constant at 5.5 seconds for every one and asking 22 participants to compare them pairwise. The functions sorted into three groups: some perceived as slower than linear, some as near-linear, and two — both accelerating, with the fastest movement near the end — perceived as faster. Their summary of the mechanism is that participants perceived bars with pauses as taking longer, that accelerating progress was strongly favoured, and that both effects were exaggerated toward the end of the process.

That result is often cited as permission to fake progress. The paper itself is more careful. Its discussion notes that there are many cases where modifying progress behaviour seems inappropriate, and that processes with known static completion conditions and stable progress are not good candidates, because a standard bar visualises them effectively and accurately. The finding licenses shaping a curve you cannot measure precisely; it does not license inventing one.

The reason dishonesty is self-defeating is that a progress indicator is a promise made repeatedly. A user who has watched a bar sit at 99% for a minute learns that this product’s bars mean nothing, and that lesson transfers to every future wait in the same product — including the ones where the number was accurate.

Where you meet it

  • An upload bar that reaches 100% the instant the last byte leaves the browser, then holds while the server processes, so the visible completion is an event the user does not care about.
  • Installers and setup wizards where the bar is driven by a step count rather than by time, so five fast steps and one slow one produce a bar that races and then stops.
  • Skeleton screens and shimmer placeholders that keep animating after a request has already failed, so a permanent error looks like an ongoing wait.
  • “Analysing your answers…” interstitials on quizzes and lead-capture funnels, where the delay is manufactured to make a pre-computed result feel considered.
  • Checkout spinners with no timeout and no cancel, where the safe user action — do not press the button twice — is the one the interface gives no reason to trust.
  • Percentages that count past 100, or that reset to zero on a silent retry, both of which reveal that the number was never a measurement.
  • Estimated-time-remaining text recalculated from the most recent instant rather than smoothed, producing figures that swing wildly enough to be useless.

Designing around it

  • Match the indicator to the length of the wait. Under roughly 0.1 second, show nothing — an indicator that flashes for one frame is worse than none. Up to about a second, no indicator is needed, though disabling the control you just pressed prevents double submission. Between about one and ten seconds, an indeterminate spinner is honest and sufficient. Beyond ten seconds you owe the user a determinate indicator and a way out, because ten seconds is where attention starts to leave.
  • Only show a percentage you actually measured. If you know the byte count, the row count or the file count, use it. If you do not, use an indeterminate indicator rather than a fabricated number — an honest spinner conveys less than a real percentage but more than a fake one, because it makes no claim it will have to break.
  • Report the phase alongside the number. “Uploading 3 of 12 files”, “Compressing”, “Waiting for the payment provider” tells the user what kind of wait this is and whether it is the kind that sometimes takes minutes. When a job has stages of very different durations, weight the bar by expected time rather than by stage count, so it does not sprint through five cheap steps and stall on the sixth.
  • Use the right markup. A native progress element with value and max attributes gets the semantics for free. If you build your own, apply role="progressbar" with aria-valuemin, aria-valuemax and aria-valuenow, and update aria-valuenow whenever the visual indicator updates. For an indeterminate state, ARIA says to omit aria-valuenow rather than setting it to zero — zero means measured-as-zero, absent means unknown. Where the raw number is meaningless on its own, add aria-valuetext with a human phrase such as “step 3 of 12, compressing”.
  • Announce transitions, not ticks. Put status messages in a container with role="status" so they are announced politely, and update it on phase changes, completion and failure only. A live region that fires on every percentage change floods a screen reader with noise and makes the rest of the page unusable. If a region of the page is loading, set aria-busy to true on that region until it is done.
  • Make the indicator move forwards, only. Never decrease the value, never reset on retry, and never let it reach 100% before the work is finished. If a retry happens, hold the current value and change the phase text to say what is being retried. A bar that goes backwards costs more trust than the extra accuracy is worth.
  • Give every wait longer than ten seconds a real cancel, and make the label true. If the button stops the request, call it Cancel; if it only hides the dialog and the work continues, say so. When cancellation is genuinely impossible after a point, disable the control and explain why rather than leaving a button that silently does nothing.
  • Shape the curve only where you cannot measure, and shape it in the direction the evidence supports: smooth out jitter, avoid pauses near the end, and let progress accelerate slightly rather than decelerate. Do this to make an unmeasurable process feel steady, not to disguise a stall. If the underlying work has stopped, the indicator should stop too, and then say what went wrong.
  • Handle failure as a first-class state. A wait that can hang needs a timeout, a message that names what timed out, and an action to take next. Test the whole thing with the network throttled and with the request forced to fail, because the stalled and failed states are the ones users encounter on bad connections and the ones that are almost never designed.

Questions

Why do progress bars get stuck at 99%?

Usually because the visible number was never measuring the slow part. A common shape is a bar driven by bytes sent, which completes when the upload finishes but before the server has processed anything, so the remaining wait has no representation left to occupy. The fix is to weight the indicator by expected time across all stages, including the ones after the transfer, and to name the current stage in text.

Is it acceptable to make a progress bar look faster than it is?

Only where you genuinely cannot measure the work. Harrison and colleagues found at UIST 2007 that accelerating bars are perceived as faster than linear ones and that pauses near the end are perceived as slower, so smoothing an unmeasurable process in that direction is reasonable. The same paper cautions against altering progress behaviour for processes whose completion conditions are known and stable, and no finding supports animating a bar that is not connected to any work at all.

When should you show a spinner instead of a percentage?

When you do not know how much work remains. An indeterminate spinner claims only that something is happening, which is a claim you can keep. Using Nielsen’s thresholds, waits under about a second usually need nothing, waits of one to ten seconds are well served by a spinner, and waits past ten seconds need a determinate indicator plus a way to cancel — which means that if you regularly exceed ten seconds, you need a real measurement rather than a longer spinner.

How do you make a progress indicator accessible?

Use the native progress element where you can, or role="progressbar" with aria-valuemin, aria-valuemax and aria-valuenow updated in step with the visual display. Omit aria-valuenow entirely while the value is indeterminate, add aria-valuetext when a bare number would not make sense, and put phase and completion messages in a role="status" region so they are announced politely rather than announcing every percentage change.

Sources

  1. Myers, B. A. (1985). The importance of percent-done progress indicators for computer-human interfaces. Proceedings of CHI ’85; also ACM SIGCHI Bulletin, 16(4), 11–17. The formal experiment establishing that people prefer having percent-done indicators during long operations. Also reports that its attempted replication of a constant-versus-variable response time preference was not statistically significant.
  2. Nielsen, J. (1993). Response Times: The 3 Important Limits. Nielsen Norman Group. (Excerpted from Usability Engineering, Academic Press.) Source of the 0.1 second, 1.0 second and 10 second thresholds, drawing on Miller (1968) and Card, Robertson & Mackinlay (1991). Determines which kind of indicator a given wait length calls for.
  3. Harrison, C., Amento, B., Kuznetsov, S., & Bell, R. (2007). Rethinking the progress bar. Proceedings of UIST ’07, 115–118. Pairwise comparison of nine progress functions at a constant 5.5 second duration with 22 participants. Establishes that pauses are perceived as slower, that accelerating progress is favoured, and that both effects are amplified near the end. Its discussion cautions against altering progress behaviour for processes with known, stable completion.
  4. W3C. Accessible Rich Internet Applications (WAI-ARIA) 1.2, progressbar role. Defines aria-valuemin, aria-valuemax and aria-valuenow for progress indicators, states that aria-valuenow should be omitted when the value is indeterminate, and describes using aria-busy on a region that is still loading.

Related patterns