Field notes
9 min read

Julieta is a QA Engineer at November Five, working across mobile and web projects. She has multiple years of experience testing and focusing on quality in the products we build.
Perceived performance is how fast an app feels. It focuses on what is on screen while the user waits, not only on the length of the wait.
In a side-by-side comparison, the news app we built started faster but still felt slower on most screens afterwards, mostly because our spinners sat on blank pages.
We measured every interaction against four response-time windows, from instantaneous to attention span.
The fast app that felt slow
While I was a QA Engineer on a white-label news mobile app project, we were asked to onboard the publisher's biggest brand. Performance was the publisher's top priority for this app.
So there I was, sitting in front of two news apps from the same publisher, assessing their performance. One was the app we built and maintained at November Five; the other was the sibling app we were about to onboard, built on a different platform with its own backend. I went through both apps side by side and timed every interaction.
On the stopwatch, our app won at startup: it reached the splash screen in 2.4 seconds, against 3.3 for the sibling app. But as I kept navigating through both apps, our app felt slower. The sibling app kept up with me while our news app made me wait, or at least that is how it felt for me.
The difference wasn’t just in the numbers; it was in what each app showed me while I waited. That gap between how fast an app is and how fast it feels is what this post is about.
What is perceived performance?
Perceived performance is how fast an app feels to the person holding the phone. Metrics can tell you how many milliseconds a request took, but the perception doesn’t always match the metrics.
That slower feeling depends a lot on what is displayed on the screen during the waiting period. A blank page with a spinner, a loading skeleton, the content you were already reading or the full news article are a few examples of how to cover the waiting period. Two screens with the same loading time can feel very different, and an app can feel faster without the network or the API getting any quicker.
It’s not about replacing real app optimisation. If the API is slow, users will notice it sooner or later.
The web side of this is already well documented; MDN and Calibre both have good introductions. In my opinion, it matters even more on mobile! People use the app on the move, on networks that change from one street to the next, and with gestures like swiping and scrolling that they obviously expect to respond to their finger straight away.
Why it matters: keeping users in the flow
To talk about "feeling fast" without making it a matter of opinion, I used the four timeframes, based on attention-span windows, from Rina A. Doherty and Paul Sorenson’s paper Keeping Users in the Flow: Mapping System Responsiveness with User Experience, as well as Calibre's article, which builds on the same idea. The descriptions below are how we used them in practice.
Window | Response time | What the user experiences |
|---|---|---|
Instantaneous | under 300 ms | The app reacts as if they are moving it directly with their finger. |
Immediate | 300 ms to 1 s | A short pause; the action still feels effortless. |
Transient | 1 to 5 s | They notice the wait but stay on task as long as something shows progress. |
Attention span | 5 to 10 s | They start to drift. Without clear feedback on what is happening, they may leave. |
A reading session in a news app is a chain of small actions: open the app, scroll the homepage, tap an article, swipe to the next one, hit a paywall and go back. Each delay is small on its own, but together they determine whether reading feels like one continuous action or a series of interruptions.
How we measured it: two lenses
I assessed perceived performance the way a user would: by going through the app and judging how it feels, while the engineers looked at where the time went. Neither was enough on its own.
The human lens: a manual assessment
I tested both news apps side by side, timed every interaction, and mapped it to one of the four timeframes defined above. Next to the timings, I wrote down how each app felt, because a window label doesn’t capture everything. Here are four of the iOS results:
Interaction | Our app | Sibling app |
|---|---|---|
Initial load to splash screen | Transient, 2.4 s | Transient, 3.3 s |
Homepage on cold start | Transient, 1.2 s with a spinner | Immediate |
Section page | Transient, around 3 s | Immediate |
Swiping to the next article | Transient | Immediate |
On Android, both apps were similar on many screens. The biggest differences were pagination on section pages and opening a paywalled article; both were slower in our iOS app, too. Opening the regional section for the first time took 5 to 10 seconds in our app: the attention-span window.
The notes explained more than the numbers. In the sibling app, spinners appeared over the content and I barely noticed them but in our app, they showed up on blank pages, so even a one-second wait got my full attention.
The machine lens: what a trace can't see
The engineers added traces to the app to see where the time went, from the request to the screen being drawn. That pointed to a slow API and slow UI processing on iOS, and gave them something concrete to fix.
Defining "loaded" from the user's side
Once the app started showing the top of the article before the rest of the content loaded (more on that below), multiple pieces of content loaded at different moments, for testing that matters. If someone asks whether a screen loads fast, I need to know which moment counts.
We settled on three, all starting from the user's tap:
First paint: the article hero is visible (headline, intro, image)
Content load: the full article can be read, without asynchronous content like embeds
Full load: everything is loaded and shown
That gave my manual checks and the engineers' numbers the same starting point and the same finish lines.
From measurements to goals
Measuring only helps if you know what the final result looks like. We ended up with a performance goals table for the app: every key screen or interaction gets a target window, a current status and possible actions on the app side.
For example, section pages and swiping to the next article sit in the transient window in our app, and the target for both is immediate. Ads are allowed to be transient, as long as the content doesn't shift.
That is what turned perceived performance from a feeling into something we could turn into actual work points.
How we improved it
The assessment and goals table became a list of possible app-side improvements. Most of them come down to two ideas: show the user something useful as early as possible, and start loading before they ask for it.
Show what you already have
When a user taps an article on the homepage, the app already knows its headline, image and teaser from the grid. So the app shows the article headline and image from that cached teaser data straight away, while the API call for the full article is still running. The API wait didn't get shorter, but the user reads the headline instead of staring at a spinner.
The same idea extends to predictive caching and prefetching: fetching what the user is likely to open next before they open it. This article on predictive API caching gives a good overview of the approach.
Spinners, skeletons and content
What I saw in the assessment, ordered from worst to best feeling:
A spinner on a blank page
A spinner on top of existing content
A skeleton screen in the shape of the content that is coming
Real content, even if it's partial or slightly old
Skeleton screens work because they show structure and progress instead of only "something is happening", but they should match the final layout or the switch becomes its own jump. The UX articles on skeleton screens vs spinners and skeleton loading design go deeper into when to use which.
Load before the user asks
Section pages loaded the next batch of articles only when the user reached the bottom, so they saw a spinner every time. Starting to load the next page halfway down the current one means it is already there by the time they get to the end.
Native and React Native
The app is native with shared Kotlin Multiplatform code, but none of the above is specific to that. On native, Instruments and the Android Studio Profiler cover time profiling, memory and energy. On React Native, the same principles apply, with one more thing to watch: work on the JavaScript thread can make scrolling stutter even when the network is fast. Sentry, which we already use for crash reporting on React Native projects at November Five, also supports custom traces with spans so that you can measure the same moments there.
Learnings
Loading time is only half of perceived performance. What users see during the wait shapes how long it feels, so measure both: how long each wait takes, and what is on screen while it happens.
Not every improvement has to make the app faster. Loading content earlier and prefetching what the reader is likely to open next, showing the heading, image, and intro while the full article loads, and loading next articles before the reader reaches the bottom of the page don’t shorten the wait, but they change how that wait feels.
As I mentioned at the beginning, performance was the publisher’s top priority. After we made these improvements, we onboarded the sibling app to our platform, and it met the standards business was looking for. Our apps now felt faster everywhere, not just at startup.
Plan the comparison before you start improving. Assess again with the same devices, steps and table, so the effect of each change shows up next to the first results.
Sources
The engineers at November Five did the instrumentation work behind the traces. Thanks for making the invisible part measurable.
Main sources:
Keeping Users in the Flow: Mapping System Responsiveness with User Experience, ScienceDirect
Psychology of Speed: A Guide to Perceived Performance, Calibre
Further reading:
Skeleton screens vs loading spinners, Onething Design
Skeleton loading screen design, LogRocket