This page used to be a case study of an unnamed “global retail brand”. Nobody could check who that retailer was or where its numbers came from, so it is gone. What follows is a plain how-to for the same topic: making the API behind a mobile app fast on real phones and real networks. Every number in the examples is invented arithmetic to show the method, not a measurement of any app.
Key Takeaways
- Mobile API performance is mostly about bytes and round trips, because phones sit on slower, less stable networks than your test servers do.
- Fix in this order: return less data, make fewer calls, let clients and CDNs cache, compress what is left, then handle token refresh without stampedes.
- Test each change with the same load before and after and compare p95 and p99, not the average.
- A load test replays HTTP requests against your backend. It does not emulate a phone’s radio or CPU, so throttle the network on a device or emulator separately.
Why is mobile API performance different from web API performance?
A mobile client talks to the same API as any other client, but it does so over cellular or weak Wi-Fi, switches networks mid-session, wakes from the background with an expired token, and often fires several requests in a burst when a screen opens. Round-trip time hurts more than bandwidth in many of those bursts, because each sequential call pays the full delay again. That is why the checklist below starts with response size and call count, not server CPU. For the portable method of measuring a launch under load, see how to measure mobile app load times with cloud load testing; for the front-end side and Core Web Vitals, see why performance testing is a priority for mobile-first businesses; for finding the slow dependency inside one endpoint, see how to cut API response times with targeted load testing. This guide covers what those three do not: the shape of the API contract itself.
How do you reduce payload size?
Return only what the screen shows. A list screen that needs a title, a price and a thumbnail URL should not receive the full product record with descriptions, nested reviews and audit fields. Options, from least to most work: a fields parameter that selects properties, a separate lightweight “summary” representation for lists, or a purpose-built endpoint per screen.
Illustrative arithmetic (invented numbers): a list response of 100 items at 2 KB each is 200 KB. On a connection that delivers 1.6 Mbit/s, 200 KB is 1,600 kbit, so about one second of transfer before the app can draw anything. Trimming each item to 0.5 KB gives 50 KB and about a quarter of a second. Your sizes and speeds will differ; the point is that transfer time scales with bytes, so measure your largest mobile responses first.
How should you paginate mobile API responses?
Never return an unbounded list. Pick a page size that fills the screen plus a little, and let the client ask for more as the user scrolls. Cursor-based pagination (the response carries an opaque next token) tends to behave better than offset pagination on big, changing datasets: a deep OFFSET makes the database skip and discard rows, and items inserted between requests can make the client see duplicates or gaps. Check the query plan of your deepest page, because that request is the one that gets slower as data grows.
How do you cut chatty calls?
A screen that needs a profile, a cart, three recommendations and a banner should not issue five sequential requests, because each waits for the one before it. Three ways out:
- Send independent calls in parallel from the client.
- Add an aggregate endpoint (sometimes called a backend for frontend) that gathers the pieces on the server, where latency between services is small.
- Move work the screen does not need immediately to a later request.
Illustrative arithmetic: six sequential calls at 150 ms each take 900 ms before the screen is complete. One aggregate call that takes 250 ms on the server side is 250 ms. If the six were independent and sent in parallel, the screen waits for the slowest, so about 150 ms plus a little client overhead. The cost of an aggregate endpoint is coupling: it changes whenever the screen does, so keep it thin.
Which caching headers should a mobile API send?
Caching lets the client or a CDN skip the request entirely or turn it into a cheap check. The vocabulary, from MDN’s HTTP caching guide (developer.mozilla.org, read 23 October 2026):
Cache-Control: max-age=<seconds>marks a response fresh for that long; a fresh stored response is used without asking the server.ETagplus the client’sIf-None-Matchlets the server answer304 Not Modifiedwith no body when nothing changed. This is the cheap fix for a response that is large but changes rarely.Cache-Control: privatekeeps personalized responses out of shared caches. MDN warns that personalized content stored in a shared cache can leak to other users, and that a cookie alone does not make a response private.Cache-Control: no-storestops all caching. MDN advises against granting it liberally.
Cache what is public and slow-changing (catalog data, configuration, images) with a sensible max-age or an ETag, mark per-user data private, and do not cache anything where staleness costs money, such as stock levels at checkout. A load test with one repeated request flatters the cache; vary the request data so the hit rate you measure resembles production.
Does compression help a mobile API?
Yes for text. JSON, HTML and similar formats compress well, and the client advertises what it supports in Accept-Encoding while the server answers with Content-Encoding and Vary: Accept-Encoding. MDN names gzip and br (Brotli) as the relevant algorithms and says not to compress already compressed formats such as images, audio and video (developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Compression, read 23 October 2026). Compression costs CPU on the server, so check that enabling it does not move your p95 at peak load, and compress at the CDN or proxy where possible. Compression and trimming stack: remove fields first, then compress what is left.
How should the app refresh its auth token?
Access tokens expire, and a phone that was in a pocket for an hour opens the app with an expired one. Two things go wrong at scale.
First, several requests fail with 401 at once and each tries to refresh. Have the client run a single refresh and make the other requests wait for it. Second, if every token has the same lifetime and many users open the app after the same event (a push notification, a morning commute), refreshes arrive in a wave. Add a little random variation to when the client refreshes, and load test the token endpoint on its own, because it is often a different service with a different capacity limit.
For public clients such as mobile apps, RFC 9700 (OAuth 2.0 Security Best Current Practice, section 2.2.2) says refresh tokens MUST be sender-constrained or use refresh token rotation (rfc-editor.org/rfc/rfc9700, read 23 October 2026). Rotation means each refresh returns a new refresh token, so a client that retries a refresh after a network drop can lock itself out if the server treats the retry as reuse. Test that retry path. The general auth testing approach is in the API performance testing OAuth 2.0 guide.
How do you test on poor networks?
Split the problem. A cloud load test shows how your backend behaves under many simultaneous requests: p95, errors and the load at which response times climb. It does not model a phone’s radio, battery or CPU. For those, throttle the connection on a test phone or an emulator (the Android Emulator and Apple’s developer tools both offer network conditioning) and record the launch or screen timings on the device. Then read both sets together: slow on-device but fast under load means too many bytes or calls; fast on-device but slow under load means a backend capacity problem. The effect of distance on results is covered in how network latency affects load testing accuracy.
How do you prove each change helped?
Run the same scenario, same request mix and same load before and after, change one thing per run, and compare p50, p95, p99 and error rate. An average that improves while p95 does not usually means a minority of requests still take the slow path. See why percentiles beat averages.
A starting k6 script that records response size and the headers discussed above (the URL and token are placeholders):
import http from 'k6/http';
import { check } from 'k6';
import { Trend } from 'k6/metrics';
const bodyBytes = new Trend('body_bytes');
export const options = {
scenarios: {
app_screen: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '2m', target: 100 },
{ duration: '5m', target: 100 },
{ duration: '1m', target: 0 },
],
},
},
thresholds: {
http_req_failed: ['rate<0.01'],
'http_req_duration{name:list}': ['p(95)<800'],
},
};
export default function () {
const headers = { Authorization: `Bearer ${__ENV.TOKEN}` };
const res = http.get('https://api.example.com/v1/products?limit=20', {
headers,
tags: { name: 'list' },
});
bodyBytes.add(res.body.length);
check(res, {
'status 200': (r) => r.status === 200,
'has cache header': (r) => !!r.headers['Cache-Control'],
});
}
The 800 ms threshold is a placeholder; set yours from your own screen budget. res.body.length is the size after decompression, so use your proxy or CDN logs for transferred bytes.
LoadFocus load testing runs k6 and JMeter scripts from 25+ cloud regions (loadfocus.com/index.md, read 23 October 2026), which lets you generate the load from where your users are. Between tests, API monitoring runs continuous checks with assertions on status code, body, headers and response time, and alerts when endpoints slow down or fail (loadfocus.com/api-monitoring, read 23 October 2026). For recurring runs, see scheduling load tests.
Frequently Asked Questions
What is a good response time for a mobile API?
There is no single number. Set a budget per screen from what the user should see and subtract network time, then give each call on the critical path a share. Treat any fixed figure you read online as a starting point and compare against your own p95 from a baseline run.
Should I use pagination or infinite scroll?
They are not alternatives at the API level. Infinite scroll is a screen behavior that still calls a paginated endpoint, usually with a cursor. The API should always return bounded pages.
Is it safe to cache API responses on the phone?
Yes for public or slow-changing data with a clear max-age or ETag. For per-user data use private, and do not cache anything where a stale value causes a wrong action, such as a price at payment time.
Do I need real devices to test mobile API performance?
For the backend, no: a load test needs only the HTTP requests the app sends. For on-device timings under slow networks, yes, or an emulator with network throttling.
Why does my API pass load tests but feel slow on phones?
Load tests from a data center remove the slow last mile. Check payload size and the number of sequential calls per screen, and measure the same screens on a throttled connection.
Sources
- MDN, HTTP caching: developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Caching (read 23 October 2026).
- MDN, HTTP compression: developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Compression (read 23 October 2026).
- RFC 9700, Best Current Practice for OAuth 2.0 Security, section 2.2.2: rfc-editor.org/rfc/rfc9700 (read 23 October 2026).
- LoadFocus: loadfocus.com/index.md and loadfocus.com/api-monitoring (read 23 October 2026).