SyncTrix logoSyncTrix
All articles
Experience11 min read

Mobile web performance: what actually matters on a mid-range Android over 4G

Your site is fast on a flagship phone on office wifi. On a three-year-old Android over a congested 4G cell, JavaScript parse time and main-thread contention dominate everything else.

By Aarav Patel
Mobile web performance: what actually matters on a mid-range Android over 4G

Development happens on fast laptops with fast connections, and that hides the single biggest cost on mobile: the CPU time to parse, compile and execute JavaScript. A mid-range Android has roughly a quarter of the single-core performance of a current flagship, and it thermally throttles below that after a few minutes of use. The same bundle that costs 200ms at your desk costs well over a second in your users' hands.

01Bytes are cheap now; CPU is not

Compression and modern networks have made transfer size a smaller share of the problem than it was. What has not improved at the same rate is the cost of turning JavaScript into running code - parse, compile and execute all happen on the main thread, and on a mid-range device that is several hundred milliseconds per hundred kilobytes of script.

That is why two pages with identical transfer sizes can differ by seconds. An image is decoded off the main thread and does not block interaction; the equivalent weight in JavaScript blocks everything, including the tap the user just made. Measure your budget in main-thread milliseconds on a target device, not in kilobytes over the wire.

Payload typeTransferMain-thread costBlocks interaction
JavaScript~0.6s1.5 - 3s parse, compile, executeYes, entirely
CSS~0.6s~100ms parse and styleBlocks render, not input
Images (WebP/AVIF)~0.6sDecoded off main threadNo
Web fonts~0.6sMinimalText invisible during swap window
Cost of 300KB of compressed payload, mid-range Android over 4G

02Test on the device, not the emulator

Browser throttling gives you a rough network simulation and a crude CPU multiplier. It does not reproduce thermal throttling, memory pressure from other applications, an older browser engine, or the way a cheap flash chip slows cache reads. Teams that only use simulated throttling consistently under-estimate real-world timings.

Buy two or three representative devices from your actual analytics - not the cheapest phone on the market, the phones your users have - and keep them on the desk. Then back that with field data segmented by device class. The aggregate p75 hides that your low-end segment is two to three times worse, and that segment is often where the growth is.

03Third-party scripts are usually the largest single cost

Tag managers, analytics, session recording, chat widgets, A/B testing, consent tools and ad scripts routinely add more main-thread work than the entire application. They are added by people who do not see the performance dashboard, they are rarely removed, and each executes with full privileges on your critical path.

Audit them with the main thread in mind rather than by transfer size. A synchronous A/B testing script that blocks render to prevent flicker can add a second before anything paints. Session recording that observes every DOM mutation degrades interaction responsiveness for the whole session. Load what you keep after interaction where possible, use facades for embeds so the heavy widget only loads on tap, and set a hard rule that a new tag requires removing an old one.

  • Measure third parties by main-thread time, not by kilobytes.
  • Replace chat and video embeds with a facade until interaction.
  • Delay analytics until after the first meaningful interaction.
  • Give tag manager containers an owner and a review cycle, or they only grow.

04Ship less JavaScript before optimising the JavaScript you ship

Route-level code splitting is the highest-yield change for most applications - a product page has no reason to carry the checkout flow, the admin views and three date libraries. Split on route, then on interaction, so components behind a click load when it happens.

Then look at what dependencies actually cost. A date library that is a hundred kilobytes for two format calls, a full icon set for eight icons, a heavy state library on a page with barely any state. And send server-rendered HTML for the first view: hydration is real main-thread work, so prefer static output or partial hydration where a route does not need interactivity. The fastest JavaScript is the JavaScript that was never on the critical path.

  • Split per route first, then per interaction.
  • Audit dependency cost per feature - many can be replaced with a few lines.
  • Server-render the first view; hydrate only what is interactive.
  • Enforce a main-thread budget in CI, not just a bundle-size budget.

05Images and fonts: mechanical wins that are still missing

Serving desktop-resolution images to a 360-pixel-wide viewport is still common, and it wastes both bandwidth and decode time. Responsive srcset with sensible breakpoints, modern formats, and explicit width and height so layout does not shift. Lazy-load everything below the fold and never lazy-load the hero, which is usually the LCP element.

Fonts cost less than people fear if handled properly: subset to the characters you need, self-host to avoid a second connection setup, preload the one or two weights used above the fold, and use font-display swap with a fallback whose metrics are matched so the swap does not reflow. Four weights and two families is a common default and rarely a deliberate decision.

06Network variability is the thing you cannot engineer away

Real 4G is not a steady pipe. Latency on a congested cell varies from tens of milliseconds to several seconds, connections drop entirely in lifts and on trains, and the radio takes time to wake from idle - so the first request after a pause carries a penalty no amount of bandwidth removes.

Design for it rather than against it. Reduce the number of round trips on the critical path, because latency dominates when bandwidth is adequate. Cache aggressively with a service worker so a repeat visit does not depend on the network at all. Make requests retryable and idempotent, keep an offline state that is honest instead of a spinner that never resolves, and give every interaction immediate local feedback so a slow network feels like a delay rather than a broken interface.

Topics

mobile web performancejavascript execution time mobilelow end android performancereduce javascript bundle sizemobile page speed 4gthird party script performance

Aarav Patel

Principal 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.