completefrontend
Open lab My progress
THE WEB PERFORMANCE FIELD GUIDE

The performance inequality gap

Most people browse on mid-range Android phones and slower networks than yours. Measure the gap, set a byte budget for them, and read the 75th percentile.

By the end, you can
  • 01
    Describe the phones people really haveSay which phone and network a typical visitor has, and why your laptop and a new iPhone aren't them.
  • 02
    Estimate the gapTurn CPU and network gaps into times for the same page, and check them against real recordings on three settings.
  • 03
    Set a budgetWork out how many bytes fit in a time target, and read Russell's budgets for JavaScript-light and JavaScript-heavy pages.
  • 04
    Read the 75th percentileWork out a 75th percentile, and say why Core Web Vitals use it, with phones and computers judged apart.

Most people aren't on your laptop

Ravi, who looks after the Leaf and Kettle website, has built a new page that lists the shop's teas. He tries it on his laptop, on the office Wi-Fi. The teas appear in about half a second. Fast enough, he thinks.

Then Asha, from the first lesson, opens the same page on the bus, on her cheap phone. For a few seconds she sees nothing. Then the shop's name appears at the top. The teas come a few seconds after that.

It's the same page and the same code. So why does Ravi wait half a second, and Asha nearly eight? And which of the two should Ravi build for?

The course measured the difference. It loaded the page in Chrome on a fast laptop. Then it loaded it on the same laptop, slowed down on purpose to act like a low-end phone on a weak 3G network. Here is when the teas appeared each time:

The same page on two devicesPop out in the code editor (opens in a new tab)JavaScript
// When the teas appeared, in ms, in two loads of the same pageconst fastLaptopMs = 564;const lowEndPhoneMs = 7628; const times = lowEndPhoneMs / fastLaptopMs;console.log(`The low-end phone waited ${times.toFixed(1)} times as long.`);
  • Line 1 is a comment. Times are in milliseconds (ms), thousandths of a second, from the start of the visit.
  • Lines 2 and 3 hold the two recorded times: 564 ms on the fast laptop, and 7,628 ms on the low-end phone.
  • Line 5 divides the slow time by the fast one.
  • Line 6 prints The low-end phone waited 13.5 times as long.

Thirteen and a half times as long, for the same page. Ravi's laptop was the wrong place to judge it. Here are both loads in one picture. In the middle is a third load: the same laptop, slowed down much the way Lighthouse, the test tool from lesson 1.1, tests a phone.

The same page loaded three ways, as bars on one time line. A fast computer: app.js arrived at 507 ms, it ran without a long task, and the main content showed at 564 ms. Lighthouse's phone: app.js arrived at 2,035 ms, it ran as a 155 ms long task, and the main content showed at 2,212 ms. A low-end phone: app.js arrived at 7,232 ms, it ran as a 366 ms long task, and the main content showed at 7,628 ms.A fast computer564 msLighthouse's phone2,212 msA low-end phonewaiting for app.js7,628 ms0 ms
Each row is the same page on one setting. The plain bar is the wait for the page's script, app.js, to arrive. The shaded bar is the phone running it, as a long task. The time at the end of each row is when the teas appeared.

The difference between Ravi's laptop and Asha's phone has a name. Alex Russell, who writes about web performance, calls it the performance inequality gap. It's the gap between the fast devices and networks developers use, and the slower ones most people have. Russell has tracked it in a series of articles since 2017. This lesson uses his 2026 edition.

Real-life analogyA recipe tested in a restaurant kitchen

A chef writes a recipe in a restaurant kitchen, with a big oven and six burners. It's ready in 20 minutes, so that's what the card says. But most people cook it at home, on a small hob with two burners. Same recipe, same steps, and it takes them far longer than the card promised.

In real life: The recipe card
In web performance: The page: its HTML, CSS and JavaScript
In real life: The restaurant kitchen with its big oven
In web performance: Ravi's fast laptop on the office Wi-Fi
In real life: "Ready in 20 minutes" on the card
In web performance: The load time Ravi measured on his laptop
In real life: A small hob in a home kitchen
In web performance: Asha's cheap phone on a weak signal
In real life: The home cook's real wait
In web performance: The visitor's real load time
The main idea

The performance inequality gap is the difference between the fast devices and networks developers test on and the slower ones most visitors have. So don't judge a page on your own laptop. Judge it on the phone and network of a typical visitor.

Which visitor, exactly? Lesson 1.2 met the 75th percentile: line everything up from best to worst, and go three quarters of the way along. Russell lines up visitors by how fast their phone and network are. The one three quarters along is the one he tells you to test with. A full quarter of visitors have something slower still.

In his 2026 edition, that visitor has:

  • a phone like the Samsung Galaxy A24 4G, an Android from 2023;
  • a network that downloads 9 megabits a second, uploads 3, and takes 100 ms for a round trip.

The next three sections take that visitor apart: the phone, its chip, and its network.

What the median phone really is

Start with the phone in the visitor's hand. Here is what Russell's sales figures show:

  • In none of the past twelve years were iPhones more than 20% of the new phones sold.
  • Cheap phones outsell expensive ones by more than 3 to 1.
  • Some low-end phones now cost less than $100.
  • People keep their phones longer than before. The median phone is now kept for more than two years.
  • Phones at the 75th percentile may be nearly three years old.

Russell sums it up: most phones in use are Androids more than two years old. So the median phone, the middle one if you line them all up, isn't a new iPhone. The phone at the 75th percentile is slower still.

Russell's test phone, the Galaxy A24 4G, came out in 2023 for $250, when the average phone sold for $353. It may be hard to buy now, so any phone with the same chip will do, such as the Galaxy A16 4G.

Russell follows four groups of phones. A phone's CPU, the chip that runs the code, has several cores: workers that can each run one thing at a time. The table compares one core of each phone, because a page's JavaScript mostly depends on one. Read each row across:

Four groups of phones (Alex Russell)
GroupPrice when newSpeed of one CPU core
Fastest iPhoneAbout $1,000 and up.The fastest phones there are, used as the yardstick here.
Fastest AndroidRussell doesn't give one.Closing the gap with the fastest iPhones, but sold in far smaller numbers than cheaper Androids.
Mid-range Android$300 to $350.More than 3.5 times slower than a recent iPhone.
Low-end Android$100 to $150, some now under $100.9 times slower than a recent iPhone, and not much faster than in 2022.

What your analytics can and can't show

Your analytics may show far more than 20% iPhones. Wikimedia, which runs Wikipedia, sees more than 40% of its phone visits come from iPhones. Russell gives two reasons:

  • People with more money, who can afford iPhones, show up more in these statistics.
  • Used and refurbished iPhones are a big and growing market. So the recent iPhone a developer carries isn't what most iPhone owners have.

So check your own numbers, but know what they can't show. People who gave up on a slow site don't appear in its usage numbers at all. Russell describes a fast-growing site that added nearly 100 KiB of JavaScript to what its pages loaded first, every month for six months. A KiB is 1,024 bytes. Each month, a smaller share of its visits came from phones. Then the team removed code nobody used, and files fetched too early. The phone visitors came back.

Real-life analogyA ramp at the shop door

A shop has three steep steps up to its door. The owner counts her customers each day, and they all walk in easily. She never sees the people with prams, wheelchairs or heavy bags who looked at the steps and went elsewhere. A ramp lets them in too, and it's easier for everyone else as well.

In real life: The steep steps up to the door
In web performance: A heavy page that needs a fast phone and network
In real life: People who look at the steps and leave
In web performance: Visitors who give up before the page loads
In real life: The owner's count of customers inside
In web performance: Your analytics: only the visits that got in
In real life: A ramp that everyone can use
In web performance: A page that's fast on a cheap phone and a slow network

So speed isn't a finishing touch for people with fast phones. It decides who can use your site at all.

Computers are part of the picture too

Laptops and desktops are only 15 to 18% of the devices sold that can browse the web. And not all of them are fast. About 80% run on batteries, so to save power and stay cool, their chips often run below full speed. Microsoft Edge sorts the computers it runs on into three tiers. Read each row across:

Computers running Microsoft Edge, by tier (Alex Russell)
TierWhat puts a computer in itShare of computers
Low-end4 CPU cores or fewer, or 4 GB of memory or less.30%
Medium4 to 8 cores, or 4 to 16 GB of memory, or a spinning hard disk.60%
High-endMore than 8 cores, more than 16 GB of memory, and a solid-state disk.10%

Nearly a third are low-end, and only 10% are high-end.

CPU speed: 3.5 to 9 times slower

Remember the one person behind the counter from lesson 1.1? That's the page's main thread. It runs the page's JavaScript one task at a time, and a tap waits until the current task is done. The main thread runs on one core at a time. So for JavaScript, how fast one core is matters more than how many there are.

Russell finds that the speed of JavaScript-built pages closely follows the speed of one core. The cheapest phones have had eight cores since 2019, but eight slow cores don't make the main thread faster.

Real-life analogyThe same hill, a sports car and a loaded bicycle

A road climbs a steep hill to a village. A sports car is at the top in a minute. A bicycle loaded with shopping takes a quarter of an hour on the same road. The hill didn't change, only the engine. And a longer hill adds a little time for the car, but a lot for the bicycle.

In real life: The hill
In web performance: The page's JavaScript: the same code on every phone
In real life: The sports car
In web performance: A recent iPhone's fast CPU core
In real life: A bicycle loaded with shopping
In web performance: A low-end Android's slow core
In real life: The time to reach the top
In web performance: How long the main thread stays busy
In real life: A longer hill
In web performance: More JavaScript: a little more time on the iPhone, a lot more on the cheap phone

Russell compares phones with Geekbench, a test that scores how fast a CPU works. On one core, a mid-range Android is more than 3.5 times slower than a recent iPhone. A low-end Android is 9 times slower. Say a tap runs 40 ms of JavaScript on a recent iPhone:

The same work on three phonesPop out in the code editor (opens in a new tab)JavaScript
const iphoneMs = 40;const phones = [  { name: "a recent iPhone", slower: 1 },  { name: "a mid-range Android", slower: 3.5 },  { name: "a low-end Android", slower: 9 },]; for (const phone of phones) {  const ms = iphoneMs * phone.slower;  console.log(`${phone.name}: ${ms} ms${ms > 50 ? ", a long task" : ""}`);}
  • Line 1 is the work's time on a recent iPhone: 40 ms.
  • Lines 2 to 6 list three phones, each with how many times slower its core is.
  • Line 9 multiplies the iPhone's time by each phone's slowdown.
  • Line 10 prints the time. When it's over 50 ms, it adds a long task.
  • It prints a recent iPhone: 40 ms, a mid-range Android: 140 ms, a long task and a low-end Android: 360 ms, a long task.

A long task, from lesson 1.1, is the main thread busy for more than 50 ms in a row. A tap during it waits. So the same code makes no long task on the iPhone, and makes Asha wait over a third of a second.

The same work as bars. A recent iPhone: 40 milliseconds. A mid-range Android: 140 milliseconds, a long task. A low-end Android: 360 milliseconds, a long task. A dashed line marks 50 milliseconds, where a long task starts.a recent iPhone40 msa mid-range Android140 msa long taska low-end Android360 msa long task50 ms
The same 40 ms of work on three phones. On both Androids it crosses the 50 ms line, so it becomes a long task, and a tap during it waits.

Why it matters: every bit of JavaScript you add costs the slowest phones the most. And the gap isn't closing on its own. Russell finds that the chips in budget phones haven't really got faster since 2022.

Time some real work on your own device. The panel does the same start-up work as the shop page later in this lesson:

Playground: the same work on slower phones
What this panel runs, shortenedJavaScript
const start = performance.now();buildShop();const ms = performance.now() - start; showTime("this device", ms);showTime("a mid-range Android, at least", ms * 3.5);showTime("a low-end Android", ms * 9);
This deviceyour device

Not timed yet.

Try it yourself
This device, and slower ones

Press Time it. The panel does the shop's start-up work 5 times on this device and keeps the middle time.

The times are real. They come from your browser's performance.now(), for the same work the shop's app.js does. The phone times multiply the middle time by Russell's single-core gaps, so they're only estimates.
  • Line 2 of the panel's code builds a catalog of 1,800 teas, sorts 200,000 prices, and builds 24 cards.
  • Lines 1 and 3 time it with performance.now(), the milliseconds since the page started loading.
  • Lines 6 and 7 multiply your time by 3.5 and by 9. They're estimates that treat your device as a recent iPhone.

The panel's heading also shows two numbers the browser reports:

  • navigator.hardwareConcurrency: the number of logical processors, or how many threads the CPU can run at once.
  • navigator.deviceMemory: about how many gigabytes of memory the device has. Only Chromium browsers report it, and they round it so sites can't use it to recognize you.

You can't carry every phone around. So, as lesson 1.1 showed, tools slow your computer down on purpose: that's throttling. Lighthouse makes the CPU 4 times slower for its phone test. Since Chrome 134, DevTools can measure your computer and offer CPU settings matched to a low-tier and a mid-tier phone. Lesson 8.3, Throttling and simulation, shows how both work.

How big is the gap on real sites? The Web Almanac 2025, HTTP Archive's yearly study of the web, tested pages on a desktop setting and a phone setting. It measured Total Blocking Time: how long, in total, long tasks kept the main thread from answering during a load. For the median page, that was 92 ms on desktop, and 1,916 ms on the phone: nearly two seconds.

Network speeds around the world

The bytes still have to reach the phone. Here the gap is about money as well as speed.

Real-life analogyThe same parcel costs more to send to some places

A shop posts the same parcel to two customers. One lives in the city. Her delivery is cheap, and the parcel arrives the next day. The other lives in a village far away. Her delivery costs more, and the parcel takes a week. The parcel is the same. The road and the price aren't.

In real life: The parcel
In web performance: The page's bytes: the same for every visitor
In real life: The delivery charge
In web performance: What the bytes cost on the visitor's data plan
In real life: The slow road to a far village
In web performance: A slow network: a low download speed and long round trips
In real life: Packing a smaller parcel
In web performance: Sending fewer bytes: cheaper and quicker for everyone

Start with the price. In lesson 1.2, a data plan was a taxi meter: every byte counts against it. The International Telecommunication Union (ITU), a United Nations agency, compares the same kind of mobile data plan in every country. People in low-income countries spend about 22 times as large a share of their income on it as people in high-income countries.

Then the road. Russell takes his network numbers from Cloudflare Radar, Cloudflare's public report on internet speed and quality around the world. At the 75th percentile, a connection:

  • downloads 9 megabits a second (Mbps);
  • uploads 3 Mbps;
  • takes 100 ms for a round trip: a message to a server and its answer back.

A megabit is a million bits, and a byte is 8 bits, so 9 Mbps brings about 1.1 million bytes a second. Each row of this table is one network you'll meet in this course:

Network settings compared
SettingDownloadUploadRound tripWhere it comes from
The 75th percentile connection9 Mbps3 Mbps100 msAlex Russell, from Cloudflare Radar
The median connectionAbout 17 MbpsNot givenNot givenAlex Russell, from Cloudflare Radar
Lighthouse's phone test1.6 Mbps0.75 Mbps150 msThe Lighthouse docs
DevTools' Slow 4G1.44 Mbps0.675 Mbps150 ms (it adds 562.5 ms)The DevTools source code
DevTools' 3G0.4 Mbps0.4 Mbps400 ms (it adds 2,000 ms)The DevTools source code

Two findings stand out:

  • The gap is growing. Since early 2024, the slowest quarter of connections improved from about 7 to 9 Mbps. Over the same time, the gap between the slower quarter and the faster quarter grew from 15 to 21 Mbps.
  • 5G isn't everywhere. 5G covered just over half the world in 2025, says the GSMA report Russell quotes. And many people need a newer, more expensive phone to use it. Russell puts it this way: "4G is a miracle, and 5G is a mirage."

What do these speeds mean for a page? Take the median mobile page, which Russell puts at 2.6 MiB, and download it on three networks:

How long 2.6 MiB takes on three networksPop out in the code editor (opens in a new tab)JavaScript
const networks = [  { name: "a fast office line", mbps: 100, rttMs: 10 },  { name: "the 75th percentile", mbps: 9, rttMs: 100 },  { name: "DevTools' Slow 4G", mbps: 1.44, rttMs: 150 },];const bytes = 2.6 * 1024 * 1024; for (const network of networks) {  const ms = network.rttMs * 4 + (bytes * 8) / (network.mbps * 1000);  console.log(`${network.name}: about ${Math.round(ms / 100) / 10} s`);}
  • Lines 1 to 5 are three networks: a made-up fast office line for comparison, the 75th percentile, and DevTools' Slow 4G.
  • Line 6 is the page's size in bytes. A MiB (mebibyte) is 1,024 × 1,024 bytes, a little more than a million.
  • Line 9 is a simple model. It spends 4 round trips to find the server, connect, make the line private and ask for the page. Then the bytes come at full speed: bytes × 8 gives bits, and 1 Mbps carries 1,000 bits each millisecond.
  • Line 10 prints a fast office line: about 0.3 s, the 75th percentile: about 2.8 s and DevTools' Slow 4G: about 15.7 s.

Same page: under half a second, or nearly sixteen. Why it matters: on a slow road, every byte you don't send saves the visitor time and money. Real loads are messier, because new connections start slowly. Lesson 4.1, Latency vs bandwidth, and lesson 4.3, TCP connections and slow start, cover that. The system design lesson Back-of-the-envelope numbers for frontend practises estimates like this.

One page, three experiences

Back to Ravi's tea page. Which part of the gap made Asha wait? The page's content is built by JavaScript, as on many sites. Its HTML holds only a header and an empty <main>:

index.html: the shop's pageHTML
<!doctype html><html lang="en"><head>  <meta charset="utf-8">  <meta name="viewport" content="width=device-width, initial-scale=1">  <title>Leaf and Kettle</title>  <link rel="stylesheet" href="styles.css">  <script src="app.js" defer></script></head><body>  <header>Leaf and Kettle</header>  <main id="app"></main></body></html>
  • Line 8 loads app.js. Its defer attribute tells the browser to run it once the HTML has been read.
  • Line 12 is the empty <main> that app.js fills in.

Here is app.js, 153 KB in all, shortened:

app.js, shortenedJavaScript
const catalog = [  { name: "Mint No. 1", garden: "Assam", notes: "bright and fresh", cents: 250 },  { name: "Green No. 1", garden: "Nilgiri", notes: "citrus and honey", cents: 1169 },  // … 1,798 more teas …]; // Start-up work, like building a search index: sort 200,000 past order prices.const prices = Array.from({ length: 200000 }, (_, i) => (i * 7919) % 1000);prices.sort((a, b) => a - b); // Sort by price, then show the intro and the cheapest 24 teas.const sorted = [...catalog].sort((a, b) => a.cents - b.cents);const gardens = new Set(catalog.map((tea) => tea.garden));const cards = sorted.slice(0, 24).map((tea) => `<article><h2>${tea.name}</h2><p>${tea.garden}: ${tea.notes}. ${tea.cents} cents.</p></article>`); document.querySelector("#app").innerHTML = `<h1>Our teas</h1><p id="intro">${catalog.length} teas from ${gardens.size} gardens, from ${sorted[0].cents} cents. Every tea is packed the week you order it and sent in a paper bag you can compost.</p>${cards.join("\n")}`;
  • Lines 1 to 5 are the catalog: 1,800 teas, written out in the file. They make up most of its size.
  • Lines 8 and 9 stand in for the start-up work a real shop does, such as building a search index. They sort 200,000 prices.
  • Lines 12 to 14 sort the teas by price and build cards for the 24 cheapest.
  • Lines 16 to 18 put a heading, an introduction and the cards into the page. Nothing useful shows until they run.

The course loaded this page in real Chrome 156, on a phone-sized screen, 5 times on each of three settings. The CPU slowdowns are compared with the recording computer, an Apple M1 Pro laptop:

  • A fast computer: Full CPU speed, DevTools' Fast 4G.
  • Lighthouse's phone: CPU 4 times slower, DevTools' Slow 4G.
  • A low-end phone: CPU 9 times slower, DevTools' 3G.

The table gives the times behind the picture at the top of the lesson. Each row is one setting, from the load with the middle LCP of its 5. LCP, from lesson 1.1, is when the largest text or image appeared: here, the shop's introduction.

The same shop on three settings
SettingCPU and networkapp.js arrivedLong taskMain content (LCP)
A fast computerFull CPU speed, DevTools' Fast 4G507 msNone564 ms, good
Lighthouse's phoneCPU 4 times slower, DevTools' Slow 4G2,035 ms155 ms2,212 ms, good
A low-end phoneCPU 9 times slower, DevTools' 3G7,232 ms366 ms7,628 ms, poor

So what made the low-end load 13.5 times as long? Subtract the fast laptop's times from the low-end ones:

Where the low-end load's extra time wentPop out in the code editor (opens in a new tab)JavaScript
// Two loads of the same page, in msconst fastLaptop = { scriptArrived: 507, longTask: 0 };const lowEndPhone = { scriptArrived: 7232, longTask: 366 }; const extraWait = lowEndPhone.scriptArrived - fastLaptop.scriptArrived;const extraWork = lowEndPhone.longTask - fastLaptop.longTask;console.log(`Extra time waiting for app.js: ${extraWait} ms`);console.log(`Extra time in long tasks: ${extraWork} ms`);
  • Line 2 is the fast laptop. app.js arrived at 507 ms, and running it made no long task.
  • Line 3 is the low-end setting. app.js arrived at 7,232 ms, and running it was a 366 ms long task.
  • Lines 5 and 6 subtract, to find how much longer each part took.
  • Lines 7 and 8 print Extra time waiting for app.js: 6725 ms and Extra time in long tasks: 366 ms.

The teas came 7,064 ms later on the low-end setting. Waiting for app.js to come over the slow network was 6,725 ms of that. The slow CPU added a long task on top.

Why it matters: on this page, the network made the biggest difference, so sending fewer bytes is the first thing to try. The CPU still matters. The same script made no long task on the laptop, a 155 ms one with Lighthouse's settings, and a 366 ms one on the low-end setting.

The filmstrips show what a visitor saw: the header first, then a wait, then the teas.

  1. Nothing drawn yet

    0 ms to 366 msA blank screen

  2. The shop's header, Leaf and Kettle, above an empty page.

    At 366 msThe header only

  3. The heading Our teas, the introduction and the first tea cards under the header.

    At 565 msThe teas

A fast computer (Full CPU speed, DevTools' Fast 4G). Recorded in Chrome 156.
  1. Nothing drawn yet

    0 ms to 1,203 msA blank screen

  2. The shop's header, Leaf and Kettle, above an empty page.

    At 1,203 msThe header only

  3. The heading Our teas, the introduction and the first tea cards under the header.

    At 2,212 msThe teas

Lighthouse's phone (CPU 4 times slower, DevTools' Slow 4G). Recorded in Chrome 156.
  1. Nothing drawn yet

    0 ms to 4,203 msA blank screen

  2. The shop's header, Leaf and Kettle, above an empty page.

    At 4,203 msThe header only

  3. The heading Our teas, the introduction and the first tea cards under the header.

    At 7,629 msThe teas

A low-end phone (CPU 9 times slower, DevTools' 3G). Recorded in Chrome 156.

You can open the shop and measure it yourself. Or download the traces for the fast, Lighthouse and low-end loads, and open them in DevTools' Performance panel.

A realistic budget for bytes and JavaScript

Ravi now knows the page is too slow for Asha. So how big may it be? He needs a performance budget: a limit set in advance, like deciding before a holiday how much you'll spend. It can limit how many bytes a page sends, or how long it takes to load.

Start from a time target and the 75th percentile network. How many bytes fit in 3 seconds?

How many bytes fit in 3 secondsPop out in the code editor (opens in a new tab)JavaScript
const rttMs = 100;const mbps = 9;const targetMs = 3000; const setupMs = 4 * rttMs;const bytesPerMs = (mbps * 1000000) / 8 / 1000;const budgetBytes = (targetMs - setupMs) * bytesPerMs; console.log("time left for bytes:", targetMs - setupMs, "ms");console.log("budget:", (budgetBytes / 1024 / 1024).toFixed(1), "MiB");
  • Lines 1 to 3 are the 75th percentile network, 100 ms and 9 Mbps, and a target of 3 seconds.
  • Line 5 spends 4 round trips before any bytes arrive, as in the download example: 400 ms.
  • Line 6 turns 9 Mbps into bytes each millisecond: 1,125.
  • Line 7 fills the rest of the time with bytes.
  • Lines 9 and 10 print time left for bytes: 2600 ms and budget: 2.8 MiB.
A time line of 3,000 milliseconds. The first 400 milliseconds are 4 round trips of 100 milliseconds. The other 2,600 milliseconds carry bytes at 9 megabits per second: about 2.8 MiB.setupabout 2.8 MiB of bytes4 round trips400 ms2,600 ms at 9 Mbps1,125 bytes each millisecond0 ms3,000 ms: the target
In the simple model, 4 round trips use the first 400 ms. The other 2,600 ms carry bytes at 9 Mbps: about 2.8 MiB. Russell's budgets are smaller, because they also count setting up connections and the phone's CPU time.

2.8 MiB is an upper limit. Russell's budget calculator counts more than this simple model, so its budgets are smaller:

  • Connections. His budgets assume the page opens two connections, and each one takes time to set up.
  • The phone's CPU time. JavaScript costs the most: after it downloads, the slow CPU still has to read it and run it.

Most bytes are like a sandwich: once you've carried it home, you can eat it. JavaScript is like a meal kit: you still have to cook it, and on a slow phone the cooking takes longest.

So the calculator sorts pages into two kinds:

  • JavaScript-light pages, where 15% of the bytes are JavaScript;
  • JavaScript-heavy pages, where half of them are.

Each row of the table is one time target, with the budget for each kind of page:

Budgets at the 75th percentile (Alex Russell)
Time targetJavaScript-light pageJavaScript-heavy page
3 seconds2.0 MiB, of which 0.3 MiB JavaScript1.2 MiB, of which 0.62 MiB JavaScript
5 seconds3.7 MiB, of which 0.57 MiB JavaScript2.3 MiB, of which 1.15 MiB JavaScript

A page that opens four connections instead of two loses another 350 KiB from the 3-second budget.

Now compare them with the web today, from the HTTP Archive figures Russell quotes:

  • The median mobile page is 2.6 MiB. That's already over the 3-second budget for a JavaScript-light page, 2.0 MiB.
  • On mobile, the median page sends 680 KiB of JavaScript. That's more than either 3-second budget allows for JavaScript: 0.3 MiB, or 0.62 MiB, which is about 635 KiB.
  • The page at the 75th percentile sends 1.3 MiB of JavaScript.

Why it matters: at least half of mobile pages are already bigger than a 3-second budget allows. Lesson 6.4, The cost of JavaScript, shows where JavaScript's CPU time goes.

Why the 75th percentile, not the average

Ravi has made his page smaller. Is it fast enough for his real visitors now? Every visit has its own time: most are fast, some slow, a few very slow. How do you sum that up in one number? A bus company has the same problem:

Real-life analogyA bus company's promise

A bus company wants to tell riders how late its buses run. The average is no good: one bus stuck in a flood for an hour makes the whole week look bad. So it promises: "3 of every 4 buses arrive within 5 minutes of the timetable." And it checks that promise on each route on its own. Otherwise the quick city buses would hide the late village ones.

In real life: Each bus's arrival
In web performance: Each page load's time
In real life: "3 of every 4 buses arrive within 5 minutes of the timetable"
In web performance: The 75th percentile: 3 of every 4 loads take this long or less
In real life: One bus stuck for an hour, which drags the average up
In web performance: A few very slow loads pull the average up
In real life: City routes and village routes, checked apart
In web performance: Phones and computers, judged apart

Page loads work the same way. To find the 75th percentile, line the loads up from fastest to slowest and go three quarters of the way along. Here it is for ten page loads:

Count three quarters of the way alongPop out in the code editor (opens in a new tab)JavaScript
const lcps = [1200, 1400, 1500, 1700, 1900, 2100, 2600, 3400, 5200, 9800];const sorted = [...lcps].sort((a, b) => a - b); const place = Math.ceil(0.75 * sorted.length);console.log(`75th percentile: load ${place} of ${sorted.length}, ${sorted[place - 1]} ms`);
  • Line 1 holds ten LCP times, one per page load.
  • Line 2 copies them and sorts them, fastest first.
  • Line 4 works out the place three quarters along. 0.75 × 10 is 7.5, and Math.ceil rounds it up to 8.
  • Line 5 prints 75th percentile: load 8 of 10, 3400 ms. An array counts from 0, so load 8 is sorted[7].

So 8 of the 10 loads took 3,400 ms or less. Rounding up to a real load like this is the nearest-rank method.

Now take the average of the same ten loads:

The average of the same loadsPop out in the code editor (opens in a new tab)JavaScript
const lcps = [1200, 1400, 1500, 1700, 1900, 2100, 2600, 3400, 5200, 9800];const average = lcps.reduce((sum, ms) => sum + ms, 0) / lcps.length;const faster = lcps.filter((ms) => ms < average).length; console.log(`average: ${average} ms`);console.log(`loads faster than the average: ${faster} of ${lcps.length}`);
  • Line 2 adds up all ten times, starting from 0, and divides by 10.
  • Line 3 counts the loads that were faster than the average.
  • Lines 5 and 6 print average: 3080 ms and loads faster than the average: 7 of 10.

The average is slower than 7 of the 10 loads. Two very slow loads, 5,200 and 9,800 ms, pulled it up, like the bus stuck in the flood. The median, the 5th load, says something else again: 1,900 ms. It looks fine, because it ignores the slower half.

Ten load times in order: 1,200, 1,400, 1,500, 1,700, 1,900, 2,100, 2,600, 3,400, 5,200 and 9,800 milliseconds. The 5th, 1,900, is the median. The 8th, 3,400, is the 75th percentile. The average, 3,080, is higher than 7 of the 10 loads.average: 3,0801,2001,4001,5001,7001,9002,1002,6003,4005,2009,800median75th percentile
Lined up, the 5th load is the median and the 8th is the 75th percentile: 8 of the 10 took 3,400 ms or less. The two slowest loads pull the average up to 3,080 ms, slower than 7 of the 10.

web.dev, Google's guide to web performance, recommends the 75th percentile of page loads, "segmented across mobile and desktop devices." So phones and computers are judged apart, like the city and village buses. Try it on 1,000 made-up loads:

Playground: the 75th percentile of phones and computers
What the panel works out, shortenedJavaScript
const sorted = [...loads].sort((a, b) => a - b);
const at = (p) => sorted[Math.ceil((p / 100) * sorted.length) - 1];

showLine("average", loads.reduce((sum, ms) => sum + ms, 0) / loads.length);
showLine("median", at(50));
showLine("75th percentile", at(75));
showLine("95th percentile", at(95));
All loads1,000 loads
Average
1,972 ms
Median (50th percentile)
1,605 ms
75th percentile
2,490 ms
95th percentile
4,520 ms
Try it yourself
What the percentiles say

All loads: 1,000 loads. The 75th percentile is 2,490 ms, so 3 of every 4 loads took that long or less, within the 2,500 ms limit. The average, 1,972 ms, sits above the median, 1,605 ms, because a few very slow loads pull it up.

Made-up loads, spread the way real load times usually are: most near the middle, with a long tail of slow ones. Real data comes from your own visitors, or from CrUX.

All the loads together have a 75th percentile of 2,490 ms. That's just inside 2.5 seconds, the limit for a good LCP. The phones alone have 2,907 ms: over the limit. Mixed together, the fast computers hid a problem for at least a quarter of the phone loads. That's why Google's tools report phones and computers apart.

Lesson 3.4, Averages lie: reading distributions, goes deeper into these numbers. Lesson 12.5, Segmenting field data, splits real visits into groups.

Benchmarks: the Web Almanac and CrUX

How does the rest of the web do on phones? The Chrome UX Report (CrUX), from lesson 1.2, is Google's public dataset of Core Web Vitals from real Chrome users' visits. The Web Almanac reads it for millions of sites. Lesson 1.2 quoted its latest result: 48% of sites had good Core Web Vitals on phones, and 56% on computers. Each row of this table is one year:

Sites with good Core Web Vitals (Web Almanac 2025)
YearPhonesComputers
202132%41%
202231%44%
202336%48%
202444%55%
202548%56%

Three findings:

  • Phones still trail computers in every year, and most sites still don't pass on phones.
  • Popular sites aren't always faster. On phones, 51% of the 1,000 most popular sites passed, but only 37% of the top 100,000.
  • Pages after the first do better. On phones, 56% of secondary pages passed, against 45% of home pages. The Web Almanac suggests one reason: files the browser saved on an earlier page can be used again.

Why it matters: if your site passes on phones, it's ahead of about half the web. To see your own site's CrUX numbers, put its address into PageSpeed Insights. CrUX is field data: timings from real visits, like the till in lesson 1.1. Lesson 3.3, Lab data vs field data, compares it with lab tests. Lesson 10.1, The Chrome User Experience Report, covers CrUX in depth. CrUX only counts Chrome users, so lesson 12.2, Field data from every browser, collects your own.

Choose a test setup

Ravi can't test on every phone. But he can test on something close to the 75th percentile visitor: slower than his laptop, but not the slowest there is. Sort each setup by where it falls:

Where does each setup fall?
  • A new laptop on office Wi-Fi
  • A new iPhone on a fast connection
  • DevTools on your laptop with Fast 4G and no CPU slowdown
  • A Samsung Galaxy A24 4G on 9 Mbps with a 100 ms round trip
  • A Samsung Galaxy A16 4G on the same 9 Mbps, 100 ms network
  • An HP 14 laptop with a Celeron N4500 on Wi-Fi
  • A Moto E phone on DevTools' 3G
  • A $100 Android on a 0.4 Mbps connection
Try it yourself
0 of 8 correct

Sort each test setup: faster than the 75th percentile, close to it, or slower.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

In practice, a setup close to the 75th percentile looks like this:

  • A real phone in the Galaxy A24 class, if you can get one. Nothing beats feeling the page in your hand.
  • In DevTools, the CPU set to the matched mid-tier phone, and a custom network of 9 Mbps down, 3 Mbps up and a 100 ms round trip.
  • Your own visitors' 75th percentile on phones, from CrUX or your own monitoring, to check your guess.

If you build for places where phones and networks are slower, Russell suggests aiming at the 90th or 95th percentile instead. Lesson 9.3, Choosing test conditions, builds a setup step by step.

Common mix-ups

  • "Phones are as fast as laptops now." No. The fastest are. But a low-end Android's core is 9 times slower than a recent iPhone's, and cheap phones outsell expensive ones by more than 3 to 1.
  • "5G has made networks fast." No. It covers only about half the world, and it needs a newer phone. The 75th percentile connection is 9 Mbps, with a 100 ms round trip.
  • "Lighthouse's phone test shows what my slowest visitors get." No. It's one setting, with the CPU only 4 times slower than the computer running it. In the recordings, the low-end setting took 3.4 times as long as Lighthouse's settings.
  • "The average load time describes my visitors." No. A few very slow loads pull it up, so it can be slower than most loads. Use percentiles.
  • "If all visits together pass, phones pass." No. Fast computers can pull the combined number down. Check phones and computers apart.
  • "My analytics show mostly iPhones, so slow phones don't matter." No. Many iPhones in use are old ones. And visitors who gave up on a slow page don't show up in your numbers at all.

Three numbers that describe a set of loads are easy to mix up. Read each row across: the number, what it tells you, and what it hides:

The average, the median and the 75th percentile
NumberWhat it tells youWhat it hides
The averageThe total divided by the number of loads.A few very slow loads pull it up, so it can describe nobody's visit.
The median (50th percentile)The middle load: half were faster, half slower.It says nothing about the slower half, where the problems are.
The 75th percentile3 of every 4 loads were this fast or faster.The slowest quarter. Look at the 95th percentile, or at groups, to see them.

Practice exercises

Answer first, then use the hints.

Exercise 1 · Warm-upLighter work

Ravi makes the work smaller: it now takes 12 ms on a recent iPhone. Which phone still gets a long task?

Starter codePop out in the code editor (opens in a new tab)JavaScript
const iphoneMs = 12;
const phones = [
  { name: "a recent iPhone", slower: 1 },
  { name: "a mid-range Android", slower: 3.5 },
  { name: "a low-end Android", slower: 9 },
];

for (const phone of phones) {
  const ms = iphoneMs * phone.slower;
  console.log(`${phone.name}: ${ms} ms${ms > 50 ? ", a long task" : ""}`);
}

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upFind the 75th percentile

    What does this print?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const lcps = [900, 1100, 1200, 1300, 1600, 2000, 2800, 6000];
    const sorted = [...lcps].sort((a, b) => a - b);
    
    const place = Math.ceil(0.75 * sorted.length);
    console.log(sorted[place - 1]);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeA 5-second budget

      This is the budget example with a 5-second target. What budget does it print?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const rttMs = 100;
      const mbps = 9;
      const targetMs = 5000;
      
      const setupMs = 4 * rttMs;
      const bytesPerMs = (mbps * 1000000) / 8 / 1000;
      const budgetBytes = (targetMs - setupMs) * bytesPerMs;
      
      console.log("time left for bytes:", targetMs - setupMs, "ms");
      console.log("budget:", (budgetBytes / 1024 / 1024).toFixed(1), "MiB");

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeFix a report that uses the average

        This report says the page is good, though three of its loads are slow. Fix it to use the 75th percentile. What does it print then?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const lcps = [700, 800, 900, 1000, 1100, 2700, 3100, 3700];
        
        function rate(lcps) {
          const typical = lcps.reduce((sum, ms) => sum + ms, 0) / lcps.length;
          return typical <= 2500 ? "good" : "not good";
        }
        
        console.log(rate(lcps));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeSegment before you conclude

          All these visits together pass the 2,500 ms limit at the 75th percentile. Which group fails on its own?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const mobile = [1900, 2300, 2600, 3200];
          const desktop = [700, 800, 900, 1000, 1100, 1200, 1300, 1400];
          
          const p75 = (values) => {
            const sorted = [...values].sort((a, b) => a - b);
            return sorted[Math.ceil(0.75 * sorted.length) - 1];
          };
          
          console.log("all visits:", p75([...mobile, ...desktop]));
          console.log("mobile:", p75(mobile));
          console.log("desktop:", p75(desktop));

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeName the long task's work in DevTools

            Open the shop page in Chrome and open DevTools. In the Performance panel, set the CPU to 4 times slower and record a reload. Or download the trace recorded with Lighthouse's settings, and load it in the panel. Find the long task in the Main track. What does DevTools call the work inside it?

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, ask who is in the slowest quarter of visits, and what their CPU and network do to the page.

              The performance inequality gap quiz · 7 questionsScore: first tries count
              1. Question 1 of 7What does the last line of the three-phones example print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const iphoneMs = 40;
                const phones = [
                  { name: "a recent iPhone", slower: 1 },
                  { name: "a mid-range Android", slower: 3.5 },
                  { name: "a low-end Android", slower: 9 },
                ];
                
                for (const phone of phones) {
                  const ms = iphoneMs * phone.slower;
                  console.log(`${phone.name}: ${ms} ms${ms > 50 ? ", a long task" : ""}`);
                }

                Choose an answer to see the explanation.

              2. Question 2 of 7Alex Russell's test phone is a Samsung Galaxy A24 4G, a phone from 2023. Why that phone?

                Choose an answer to see the explanation.

              3. Question 3 of 7Your analytics say most of your visitors use iPhones. Can you skip testing on slow phones?

                Choose an answer to see the explanation.

              4. Question 4 of 7What does the 75th percentile example print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const lcps = [1200, 1400, 1500, 1700, 1900, 2100, 2600, 3400, 5200, 9800];
                const sorted = [...lcps].sort((a, b) => a - b);
                
                const place = Math.ceil(0.75 * sorted.length);
                console.log(`75th percentile: load ${place} of ${sorted.length}, ${sorted[place - 1]} ms`);

                Choose an answer to see the explanation.

              5. Question 5 of 7Why does web.dev judge Core Web Vitals at the 75th percentile, with phones and computers apart?

                Choose an answer to see the explanation.

              6. Question 6 of 7In the three recorded loads of the same shop, what made the low-end load's main content come so much later?

                Choose an answer to see the explanation.

              7. Question 7 of 7Why is Alex Russell's budget for a JavaScript-heavy page smaller than for a JavaScript-light page?

                Choose an answer to see the explanation.

              Key takeaways

              • The performance inequality gap is the difference between the devices and networks developers test on and the slower ones most people have.
              • Most phones in use are Androids more than two years old. Russell's 75th percentile test phone is a Samsung Galaxy A24 4G.
              • On one CPU core, a low-end Android is 9 times slower than a recent iPhone, and a mid-range one more than 3.5 times. The same JavaScript costs the slowest phones the most.
              • The 75th percentile connection downloads 9 Mbps, uploads 3 Mbps, and takes 100 ms for a round trip. The same bytes cost some people far more than others.
              • The same shop page showed its teas in 564 ms on a fast laptop and 7,628 ms on a low-end setting. Most of the difference was waiting for the network.
              • At the 75th percentile, a 3-second page can afford about 2.0 MiB if it's light on JavaScript, and 1.2 MiB if half of it is JavaScript.
              • Judge speed at the 75th percentile, with phones and computers apart. The average hides the slow visits, and your analytics miss the people who gave up.

              Remember the one-liner.
              Build for the phone and network that a quarter of your visitors are slower than, not for the laptop on your desk.

              Sources

              The facts on this page come from these articles, datasets and docs:

              Coming next: Your toolkit and the course project. It sets up the browsers and DevTools settings you'll use for the rest of the course, and takes a first baseline.

              CompleteFrontend Clear concepts. Working examples.