completefrontend
Open lab My progress
THE WEB PERFORMANCE FIELD GUIDE

HTTP/3 and QUIC

QUIC moves HTTP off TCP onto UDP. See real connections open a round trip sooner, why one lost packet stops stalling every file, and how browsers find HTTP/3.

By the end, you can
  • 01
    Explain what QUIC fixesSay why one lost packet holds up every file over HTTP/2, and how QUIC's streams, its single handshake and its connection IDs help.
  • 02
    Get browsers to use HTTP/3Explain how a browser learns about HTTP/3 from an Alt-Svc header or a DNS HTTPS record, and why first visits often use HTTP/2.
  • 03
    Check whether it helpsRead the protocol from the browser's own timings, and compare field data from groups chosen at random.

HTTP over a new transport

You visit completefrontend.com for the first time. You close the browser, open it again, and come back. Then you do it once more. Nothing about the site changed between those three visits. The course did exactly this in Chrome, and wrote down which version of HTTP each visit used:

Three visits, two versions of HTTPPop out in the code editor (opens in a new tab)JavaScript
// A fresh Chrome visits completefrontend.com three times.// The browser is closed in between, so every visit opens a new connection.const visits = ["h2", "h3", "h3"]; visits.forEach((protocol, index) => {  console.log(`Visit ${index + 1} came over ${protocol}.`);});
  • Lines 1 and 2 are comments. Closing the browser between visits means each visit has to open a new connection to the server.
  • Line 3 holds what Chrome reported for each visit. h2 is the short name for HTTP/2, and h3 is the short name for HTTP/3.
  • Lines 5 to 7 print one sentence per visit: Visit 1 came over h2., Visit 2 came over h3. and Visit 3 came over h3.

The course ran these three visits five times, with a fresh browser each time. It got the same answer every time. So there are two questions. What is HTTP/3, and what did the later visits gain from it? And why did the first visit miss out? The rest of this lesson answers both, in that order.

Start with the name. HTTP is the language browsers and servers speak: "GET me this page", "200 OK, here it is". HTTP/1.1 and HTTP/2 showed how HTTP/2 lets many files share one connection. HTTP/3 is the next version.

Here is the surprise. HTTP/3 doesn't change the language. Methods like GET, headers, and status codes like 200 work the same way as in HTTP/2. What changes is everything underneath: how the bytes travel. The layers that carry HTTP's bytes are called its transport.

Over HTTP/2, the bytes travel on two layers you met in earlier lessons:

  • TCP carries the bytes. It numbers them, sends again whatever goes missing, and hands them over in order. TCP connections and slow start covered it.
  • TLS puts the bytes in a secret code, so nobody in between can read them. TLS without the wait covered it.

HTTP/3 replaces both with one new layer, called QUIC. QUIC does TCP's jobs and TLS's jobs together. It sends its packets over UDP, a very simple way to send data across the internet. UDP just sends each packet off. It doesn't number them, send them again, or keep them in order. So QUIC does all of that itself.

Two stacks of layers side by side. Visit 1, over h2: HTTP/2 on top, TLS 1.3 under it, and TCP at the bottom. Visits 2 and 3, over h3: HTTP/3 on top, QUIC with TLS 1.3 built in under it, and UDP at the bottom. The HTTP on top works the same way in both.Visit 1: h2HTTP/2TLS 1.3TCPVisits 2 and 3: h3HTTP/3QUIC, with TLS 1.3 insideUDPsame HTTPBoth send their packets across the same internet.
Read each stack from the top. HTTP is the same in both. Under it, QUIC does the jobs of TCP and TLS together, and UDP carries its packets.
Real-life analogySame parcels, a new courier

Leaf and Kettle, the tea shop, changes the courier it uses for deliveries. Your order is exactly the same: the same tea, the same box, the same label with your address. Only the vans and the drivers are new, and the new company seals every box itself.

In real life: What's in the box, and the label on it
In JavaScript: HTTP: methods, headers and status codes
In real life: The old courier
In JavaScript: TCP, with TLS on top
In real life: The new courier
In JavaScript: QUIC, which sends its packets over UDP
In real life: Boxes sealed by the courier itself
In JavaScript: TLS 1.3, built into QUIC

What does the new courier do better? Three things, one per section:

  • Streams. A lost packet holds up only its own file, not every file on the connection.
  • The handshake. A new connection is ready one round trip sooner.
  • Migration. A connection can carry on when a phone moves from Wi-Fi to mobile data.

Then comes the catch from the first visit: a browser only uses HTTP/3 once it knows the site speaks it.

The main idea

HTTP/3 is the same HTTP, carried by QUIC instead of TCP and TLS. A lost packet holds up one file instead of all of them. A new connection is ready one round trip sooner. A connection can move from one network to another. But a browser only uses HTTP/3 once it has learned that the site speaks it.

For the three versions side by side, without a stopwatch, see the system design lesson HTTP/1.1, HTTP/2, and HTTP/3.

Head-of-line blocking at the TCP level

Back to visit 1, over HTTP/2. Say the page needs three files: a stylesheet (the CSS), a script, and a picture. HTTP/2 sends all three over one TCP connection.

The files travel as packets, small chunks that cross the network one by one. HTTP/2 sends them taking turns: a packet of CSS, a packet of script, a packet of picture, and round again. Each file travels as its own numbered stream, so the browser knows which packet belongs to which file.

Now say one packet is lost on the way: packet 1, part of the CSS. What happens to the rest?

TCP waits for the gap

TCP connections and slow start showed TCP's rule. It hands bytes to the browser strictly in order, the way Ravi read your long letter page by page. If a packet is missing, everything after it waits until a new copy arrives. That takes at least one round trip.

Here is what that rule does to the script, with packets arriving every 10 ms:

Over TCP, the script waits for the CSSPop out in the code editor (opens in a new tab)JavaScript
// Packets arrive every 10 ms. Packet 1 was lost on the way,// so its copy arrived one round trip, 100 ms, late.const arrivedAt = { 1: 110, 2: 20 }; // Packet 2 is the script's. TCP hands it over once packet 1 is in too.const handedOver = Math.max(arrivedAt[1], arrivedAt[2]);console.log(`Over TCP, the browser gets the script's packet 2 at ${handedOver} ms.`);
  • Lines 1 and 2 are comments. The lost packet was sent again, so its copy arrived one round trip late. Here a round trip is 100 ms.
  • Line 3 holds when the first two packets reached the browser: packet 1's copy at 110 ms, and packet 2 at 20 ms.
  • Line 6 is TCP's rule. Packet 2 can't be handed over until packet 1, which comes before it, is in. So it takes the later of the two times.
  • Line 7 prints Over TCP, the browser gets the script's packet 2 at 110 ms.

The script's packet was there at 20 ms. It sat waiting for 90 ms, for a CSS packet it has nothing to do with. TCP can't do better, because it doesn't know about files or streams. It only sees one long line of bytes.

RFC 9114, the standard that defines HTTP/3, says it plainly. With HTTP/2, a lost packet "causes all active transactions to experience a stall". A transaction is one request and its response. This is head-of-line blocking at the TCP level: the packet at the head of the line holds up everyone behind it.

QUIC waits only inside a file

Real-life analogyA van for each parcel

You order a teapot, a tin of tea and a cup from Leaf and Kettle. The old courier carries all three in one van and hands them over in packing order, so a missing teapot keeps the others in the van. The new courier sends each parcel in its own van. If the teapot's van is stuck in traffic, the tea and the cup still arrive on time.

In real life: The teapot, the tea and the cup
In JavaScript: The CSS, the script and the picture: three streams
In real life: One van, unloaded in packing order
In JavaScript: One TCP connection, which hands bytes over in order
In real life: A van for each parcel
In JavaScript: QUIC, which delivers each stream on its own
In real life: The van stuck in traffic
In JavaScript: The stream that lost a packet

QUIC knows about streams. It keeps each stream's bytes in order on their own, separately from the other streams. So a lost packet holds up only the stream it belongs to:

Over QUIC, the script doesn't waitPop out in the code editor (opens in a new tab)JavaScript
// The same two packets. Packet 1 is the CSS's, packet 2 the script's.const arrivedAt = { 1: 110, 2: 20 }; // QUIC waits only for earlier packets of the same file.// The script has none before packet 2, so nothing holds it up.const handedOver = arrivedAt[2];console.log(`Over QUIC, the browser gets the script's packet 2 at ${handedOver} ms.`);
  • Line 2 holds the same two times as before.
  • Lines 4 and 5 are comments with QUIC's rule. The script has no packets before packet 2, so there's nothing for it to wait for.
  • Line 6 takes packet 2's own time.
  • Line 7 prints Over QUIC, the browser gets the script's packet 2 at 20 ms.

Same packet, same network, and the script got it 90 ms sooner. Inside one stream, QUIC still keeps the bytes in order: the CSS's second packet still waits for its first. Here are all six packets, both ways:

Six packets, two each for the CSS, a script and a picture, interleaved. Packet 1, part of the CSS, is lost. Over TCP, packets 2 to 6 are held until packet 1's copy arrives a round trip later. Over QUIC, only packet 4, the CSS's other packet, waits; the script and the picture are handed over at once.TCP1CSS2script3picture4CSS5script6pictureall 5 packets after it wait for its copyQUIC1CSS2script3picture4CSS5script6pictureonly the CSS's own packet 4 waits
Packet 1, part of the CSS, is lost. Shaded packets have arrived but are held back. TCP holds back all five packets after the gap. QUIC holds back only the CSS's own packet.

Lose any packet

Which packet is lost changes who waits. Pick one in the playground. It runs the whole model: six packets, one every 10 ms, and a lost one that comes again a round trip later.

Playground: lose a packet
When each file is readyJavaScript
const roundTripMs = 100;const lost = 1;const files = { CSS: [1, 4], script: [2, 5], picture: [3, 6] };const arrives = (packet) => packet * 10 + (packet === lost ? roundTripMs : 0); for (const [file, packets] of Object.entries(files)) {  const last = Math.max(...packets);  // TCP: every packet up to the file's last one must be in.  const tcp = Math.max(...[1, 2, 3, 4, 5, 6].slice(0, last).map(arrives));  // QUIC: only the file's own packets must be in.  const quic = Math.max(...packets.map(arrives));  console.log(`The ${file} is ready at ${tcp} ms over TCP, ${quic} ms over QUIC.`);}
Packet 1 lost100 ms round trip
CSSTCP 110 ms · QUIC 110 ms
scriptTCP 110 ms · QUIC 50 ms
pictureTCP 110 ms · QUIC 60 ms
Try it yourself
Who waits

Packet 1 belongs to the CSS. It's lost, and its copy comes 100 ms later. Over TCP, every packet after it waits for that copy, so the script and the picture are held up too. Over QUIC, only the CSS waits.

A model, like the examples: a packet arrives every 10 ms, and a lost packet's copy arrives one round trip later. TCP hands a packet over once every packet before it is in. QUIC waits only for earlier packets of the same file.
  • Lines 1 to 4 of the panel's code set up the model: the round trip, the lost packet, each file's two packets, and when each packet arrives.
  • Line 9 is TCP's rule. A file is ready once every packet up to its last one is in, whichever file they belong to.
  • Line 11 is QUIC's rule. A file is ready once its own packets are in.
  • With packet 1 lost, the first line it prints is The CSS is ready at 110 ms over TCP, 110 ms over QUIC.
  • The second is The script is ready at 110 ms over TCP, 50 ms over QUIC.
  • The third is The picture is ready at 110 ms over TCP, 60 ms over QUIC.

Pick No loss, and TCP and QUIC deliver every file at the same moment. So on a clean network, QUIC's streams change nothing. On a network that loses packets, like a phone on a busy mobile network, they make the difference between one file waiting and every file waiting.

QUIC's combined handshake

Back to the first visit, and to Ravi from Latency vs bandwidth. On his line, every message takes 150 ms to reach the server and come back. That's one round trip. Before the browser can ask for a page over HTTP/2, it waits for two of them, one for each handshake. A handshake is the short exchange of messages that sets something up before any page data flows:

  • TCP's handshake says hello and opens the connection.
  • TLS 1.3's handshake agrees on a secret code.
Two handshakes on Ravi's linePop out in the code editor (opens in a new tab)JavaScript
const roundTripMs = 150; // Ravi's round tripconst tcp = 1; // TCP says helloconst tls = 1; // TLS 1.3 agrees on a secret code const wait = (tcp + tls) * roundTripMs;console.log(`Over TCP and TLS, the request waits ${wait} ms.`);
  • Line 1 is Ravi's round trip: 150 ms.
  • Lines 2 and 3 count the round trips before the request: one for TCP, and one for TLS 1.3.
  • Line 5 adds them up and multiplies by the round trip.
  • Line 6 prints Over TCP and TLS, the request waits 300 ms.

Why two? Because TCP and TLS are separate layers. TLS can't start until TCP has finished saying hello. QUIC is one layer that does both jobs, so it can do them at the same time.

Its very first message says hello and carries the browser's key share, its part in making the secret code. The server answers with its own key share and its certificate. After that one round trip, both sides have the code, and the request can go.

Real-life analogyOne breath at the counter

A new customer at Leaf and Kettle's counter says hello and waits, then asks to pay by card and waits again, before ordering. A quicker customer says hello and asks about the card in one breath, so they wait only once. A regular greets, says "card as usual" and orders, all in one breath.

In real life: Saying hello, then waiting
In JavaScript: TCP's handshake: one round trip
In real life: Asking to pay by card, then waiting
In JavaScript: TLS 1.3's handshake: one more round trip
In real life: Hello and the card question in one breath
In JavaScript: QUIC's handshake: both jobs in one round trip
In real life: The regular who also orders in that breath
In JavaScript: 0-RTT: a returning browser sends its request with its first message

The quicker customer is QUIC. On Ravi's line:

One handshake on Ravi's linePop out in the code editor (opens in a new tab)JavaScript
const roundTripMs = 150; // Ravi's round tripconst quic = 1; // QUIC says hello and agrees on the code, together const wait = quic * roundTripMs;console.log(`Over QUIC, the request waits ${wait} ms.`);
  • Line 2 counts one round trip, for QUIC's hello and TLS 1.3's code together.
  • Line 5 prints Over QUIC, the request waits 150 ms.

That's 150 ms saved for Ravi, on every new connection. And the regular? Like TLS 1.3 in TLS without the wait, QUIC supports 0-RTT, short for zero round-trip time. A browser that has connected before sends its request with its very first message.

The same warning applies. Someone on the network could record that early request and send it again. So a server should accept it only for requests that are safe to repeat, like "send me the home page".

What the recording shows

Does a real connection save a whole round trip? The course had Chrome open new connections to completefrontend.com. It forced HTTP/2 five times and HTTP/3 five times, each in a fresh browser, and kept the middle time of each five.

Each time is the connection setup, from connectStart to connectEnd in the browser's own timings. To know how long one round trip was, the course also timed a plain TCP connection, which takes exactly one.

Real connections, both waysPop out in the code editor (opens in a new tab)JavaScript
// New connections to completefrontend.com: the middle time of five tries each.const roundTripMs = 22.6;const http2Setup = 58.8;const http3Setup = 31; const saved = http2Setup - http3Setup;console.log(`HTTP/3 was ready ${saved.toFixed(1)} ms sooner.`);console.log(`That's ${(saved / roundTripMs).toFixed(1)} round trips.`);
  • Line 2 is one round trip to the server: 22.6 ms.
  • Lines 3 and 4 are the two setups: 58.8 ms over HTTP/2, and 31 ms over HTTP/3.
  • Line 6 subtracts one from the other.
  • Lines 7 and 8 print HTTP/3 was ready 27.8 ms sooner. and That's 1.2 round trips.

About one round trip, just as counted. The course timed www.cloudflare.com the same way. HTTP/3 was ready 22.8 ms sooner there, again about one round trip, which took 24.7 ms. The picture shows the two setups as round trips:

Two rows of round trips before the first request. HTTP/2: one for TCP, one for TLS 1.3, then the request. HTTP/3: one for QUIC with TLS 1.3 built in, then the request. Recorded to completefrontend.com, setting up the connection took 59 ms over HTTP/2 and 31 ms over HTTP/3.HTTP/2TCPTLS 1.3requestconnected: 59 msHTTP/3QUIC + TLSrequestconnected: 31 ms
Each shaded box is one round trip before the request. QUIC says hello and agrees on the secret code in the same round trip, so HTTP/3 has one box fewer.

One round trip is small on a fast line like the course's: about 23 ms. On Ravi's line, it's 150 ms. So QUIC's handshake helps most where round trips are long: far-away servers, mobile networks, and short visits that spend much of their time connecting.

Connection migration between networks

Asha is reading this course on her phone at home, on Wi-Fi. She walks out to the bus stop, and her phone moves from the Wi-Fi to mobile data. What happens to the page that was halfway through loading?

It depends on how the server recognises her connection. Every device on a network has an IP address, the number other computers use to reach it. Each connection also has a port, a second number that keeps a device's connections apart. On the Wi-Fi, Asha's phone has one address. On mobile data, it gets a different one.

TCP: a new address is a new connection

TCP knows each connection by four things: the address and the port at each end. Change one, and to TCP it's a different connection. So when Asha's address changes, her TCP connections are gone:

TCP sees a new addressPop out in the code editor (opens in a new tab)JavaScript
// Asha's phone, at home and then at the bus stop.const onWifi = { address: "192.0.2.10:52000" };const onMobile = { address: "198.51.100.23:61000" }; // TCP knows a connection by its addresses.const same = onWifi.address === onMobile.address;console.log(same ? "TCP: the same connection." : "TCP: a new connection, with a new handshake.");
  • Lines 2 and 3 are the phone's address on each network. The number after the colon is the port. Both are example addresses.
  • Line 6 is TCP's check: is the address the same?
  • Line 7 prints TCP: a new connection, with a new handshake.

A new connection means the whole setup again: TCP's hello, TLS's secret code, and slow start from the beginning. Any request that was halfway through starts over.

QUIC: the connection has its own ID

Real-life analogyA call that follows your caller ID

You're on the phone to Leaf and Kettle as you walk out of the house, and your phone moves from the home Wi-Fi to mobile data. The call carries on. It's tied to who you are, your caller ID, not to the room you're calling from.

In real life: The room you're calling from
In JavaScript: The phone's IP address and port
In real life: Walking out onto mobile data
In JavaScript: The phone's address changing
In real life: Your caller ID
In JavaScript: A QUIC connection ID
In real life: The call carrying on
In JavaScript: Connection migration: the same connection, on a new network

QUIC gives each connection a connection ID: a label that goes on every packet. The server hands out several IDs for the same connection, and it recognises the connection by any of them.

RFC 9000, the standard that defines QUIC, says connection IDs let "connections survive changes to endpoint addresses". Moving a connection to a new network this way is called connection migration.

QUIC sees the same connectionPop out in the code editor (opens in a new tab)JavaScript
// The server gave Asha's connection two IDs to use.const connectionIds = ["c7f3a1", "4be902"];const onWifi = { address: "192.0.2.10:52000", id: "c7f3a1" };const onMobile = { address: "198.51.100.23:61000", id: "4be902" }; // QUIC knows a connection by its IDs, not by its addresses.const same = connectionIds.includes(onMobile.id);console.log(same ? "QUIC: the same connection, carrying on." : "QUIC: a new connection.");
  • Line 2 holds the two IDs the server gave this connection.
  • Lines 3 and 4 are the phone on each network. Its address changes, and so does the ID it puts on its packets.
  • Line 7 is QUIC's check: is the ID one of this connection's?
  • Line 8 prints QUIC: the same connection, carrying on.

Why does the phone switch to another ID at all? Privacy. If it kept the same ID, anyone watching both networks could tell it was the same phone. RFC 9000 has it use a different ID on each network, so only the server can link the two.

A phone moves from Wi-Fi, address 192.0.2.10, to a mobile network, address 198.51.100.23. Over TCP, whose connections are known by their addresses, it needs a new connection and handshake. Over QUIC, whose connections are known by connection IDs, the same connection carries on.TCPWi-Fi 192.0.2.104G 198.51.100.23reconnecta new handshake, and requests in flight start overQUICWi-Fi 192.0.2.104G 198.51.100.23carries onthe server knows it by another of its IDs
Both rows start on the Wi-Fi and move to 4G. TCP sees a new address and has to start again. QUIC sees a connection ID it knows and carries on.

For a phone on the move, that's a download that carries on instead of starting again. And it skips a new handshake on a mobile line, where round trips are often long.

How browsers discover HTTP/3

Now back to the puzzle from the start of the lesson. The site speaks HTTP/3, so why did visit 1 come over HTTP/2? Because the browser didn't know yet.

Why not just try HTTP/3 first? The Web Almanac, a yearly study of millions of websites, explains. If a browser tried QUIC on a server that doesn't speak it, or on a network that blocks UDP, nothing would answer. The browser would wait for the attempt to time out, which can take several seconds. Only then would it open a TCP connection instead.

So a browser only starts HTTP/3 once the site has told it, "I speak HTTP/3". A site can say so in two ways.

The Alt-Svc header

Alt-Svc, short for alternative service, is a header the server sends with a response. It says: "I also speak HTTP/3, over here." For example, completefrontend.com sends h3=":443";ma=86400:

  • h3=":443" means HTTP/3 is on port 443 of this same server.
  • ma=86400 means the browser can remember that for 86,400 seconds, which is one day.

The catch is in the timing. The header arrives with the first response. By then, the first connection is already open over TCP. Only the next connection can use HTTP/3.

A DNS HTTPS record

DNS lookups met the other way: an HTTPS record. It comes with the DNS answer, before any connection opens. Its alpn=h3,h2 lists the protocols the site speaks, best first. That lesson compared it to a phone book that prints a shop's direct line, so even your first call can use it.

This site, completefrontend.com, has no HTTPS record. Cloudflare's site, www.cloudflare.com, has one, and it starts 1 . alpn=h3,h2.

Read this table across: each row is one way to announce HTTP/3, what it says, and which connections can use it.

Two ways to say HTTP/3 is here
Which announcementWhat does it say?Which connections can use HTTP/3?
The Alt-Svc headerIt comes with the server's response. h3=":443" means HTTP/3 is on port 443, and ma says how many seconds to remember it.Every connection after the first. The first one is already open when the header arrives.
A DNS HTTPS recordIt comes with the DNS answer. alpn=h3,h2 lists the protocols the site speaks, best first.Every connection, the first one too, if the DNS answer arrives in time.

Put the two together, and you can predict a site's visits:

When HTTP/3 startsPop out in the code editor (opens in a new tab)JavaScript
// What completefrontend.com tells browsers about HTTP/3.const site = { altSvc: true, httpsRecord: false }; // An HTTPS record arrives before the first connection opens.const firstVisit = site.httpsRecord ? "HTTP/3" : "HTTP/2";// Alt-Svc arrives with the first response, in time for the next connection.const laterVisits = site.altSvc || site.httpsRecord ? "HTTP/3" : "HTTP/2"; console.log(`The first visit comes over ${firstVisit}, later visits over ${laterVisits}.`);
  • Line 2 is what completefrontend.com announces: Alt-Svc, but no HTTPS record.
  • Line 5: only an HTTPS record can help the first visit, because only it arrives before the first connection opens.
  • Line 7: either one helps later visits.
  • Line 9 prints The first visit comes over HTTP/2, later visits over HTTP/3.

That's exactly what the course saw. For each site, it played a returning visitor five times: three visits each, closing the browser in between.

  • completefrontend.com: visit 1 came over HTTP/2 in 5 of 5 runs. Every later visit came over HTTP/3.
  • www.cloudflare.com, with its HTTPS record: visit 1 came over HTTP/3 in 4 of 5 runs. Every later visit came over HTTP/3.

Why did one of Cloudflare's first visits still use HTTP/2? The likely reason is a race. Even when Chrome knows about HTTP/3, its network code can start a TCP connection at the same time, as a backup in case QUIC is slow or blocked. The code's own words are "race the two jobs". Chrome uses whichever connection is ready first. It's like ringing a shop on two phones and talking on whichever one answers first. Once in a while, TCP wins.

And on a network that blocks UDP, QUIC never connects. RFC 9114 says browsers should then fall back to a TCP version of HTTP. The site still works there. It just doesn't get QUIC's gains.

Discovery also explains a big gap in the Web Almanac's numbers. It found that about 30% of sites support HTTP/3. Yet only 7 to 9% of the home pages it loaded came over HTTP/3. The Almanac loads every page as a first visit, so a site that only sends Alt-Svc always shows up as HTTP/2. If you turn HTTP/3 on, add an HTTPS record too, so first visits can use it.

Measuring whether it helps your users

Say your site now speaks HTTP/3. Is it faster for your visitors? On a good network, a little. On slow and lossy networks, more.

When Chrome turned HTTP/3 on, its team compared it with HTTP/2 over TLS 1.3 and TCP. They reported:

  • Google search answered over 2% sooner.
  • YouTube videos spent over 9% less time stopped to load more, which is called rebuffering.
  • Data arrived over 3% faster on computers, and over 7% faster on phones.

The bigger gain on phones fits what this lesson showed. Lost packets and extra round trips cost the most on mobile networks.

What about your own site? Find out with field data: measurements sent from real visitors' browsers. Each visit can report which protocol it used. The browser keeps it in nextHopProtocol, in the page's list of timings. Press Look to read it for this page:

Playground: this page's connection
What Look does, shortenedJavaScript
const [page] = performance.getEntriesByType("navigation"); const report = { protocol: page.nextHopProtocol, connectMs: page.connectEnd - page.connectStart };console.log(report);
This pagenot read yet

Nothing read yet.

Try it yourself
Your page

Press Look to read the two values you'd send with your field data: the protocol this page came over, and how long its connection took to set up.

Read from your own browser. nextHopProtocol is the protocol. connectStart to connectEnd is the connection setup, and it's 0 when the page reused a connection that was already open.
  • Line 1 of the panel's code gets this page's list of timings, called its navigation entry.
  • Line 3 builds the report: the protocol, and how long setting up the connection took.
  • A setup of 0 means the page used a connection that was already open.

To check one request by hand, open the Network panel in DevTools and add its Protocol column.

Compare groups chosen at random

Now the tricky part. You split your field data into HTTP/3 visits and HTTP/2 visits. Then you compare their LCP, the moment the biggest thing on the screen appears. It's tempting to read the difference as what HTTP/3 did. But the two groups aren't alike:

  • The HTTP/2 group holds every first visit, if your site only sends Alt-Svc.
  • It also holds every visit from a network that blocks UDP.
  • So the HTTP/3 group is mostly return visits, on networks that let QUIC through.
Real-life analogyTossing a coin for the new tea

Leaf and Kettle wants to know if its new tea brings customers back. Comparing the people who chose it with those who didn't isn't fair: the ones who chose it were mostly regulars, who come back anyway. So the shop tosses a coin for each customer, and only those who get heads try the new tea.

In real life: Customers who chose the new tea
In JavaScript: Visits that happened to use HTTP/3
In real life: They were mostly regulars already
In JavaScript: HTTP/3 visits are mostly return visits, on networks that allow UDP
In real life: A coin toss for each customer
In JavaScript: Turning HTTP/3 on for a random share of visitors

A fair test does the same. It turns HTTP/3 on for a random share of visitors, and leaves it off for the rest. Then the main difference between the two groups is HTTP/3. Chrome rolled HTTP/3 out in steps like this. It started with a quarter of its users, and watched the numbers before giving it to more.

Compare the two groups' LCP and other metrics. Look hardest at slow and lossy connections, because that's where QUIC helps most.

Which QUIC feature?

Each problem below is solved by a different part of HTTP/3. Sort each one into the part that solves it:

Which QUIC feature?
  • On a busy phone network, one lost packet holds up every file
  • A lost picture packet holds up the stylesheet behind it
  • Every new connection to a far-away server waits two round trips before the request
  • Short visits spend most of their time connecting
  • A download starts over when a phone leaves the Wi-Fi
  • Requests fail as a train moves from one mobile network to another
  • First visits always come over HTTP/2
  • The browser learns about HTTP/3 only after its first request
Try it yourself
0 of 8 correct

Sort each problem by the part of HTTP/3 that solves it.

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

Common mix-ups

  • "HTTP/3 is always much faster." No. On a good network, it saves about one round trip per new connection. It helps most on slow, lossy and mobile networks.
  • "QUIC is unreliable, because it runs over UDP." No. UDP only carries the packets. QUIC itself numbers them, sends lost ones again, and keeps each stream in order.
  • "HTTP/3 has no head-of-line blocking." No. Inside one stream, a lost packet still holds up the rest of that stream. It just doesn't hold up the other streams.
  • "Turn HTTP/3 on, and every visit uses it." No. With only Alt-Svc, first visits still come over HTTP/2. And networks that block UDP never use HTTP/3.
  • "QUIC isn't encrypted." No. QUIC always uses TLS 1.3. It's built into the handshake.
  • "HTTP/3 changes how HTTP works." No. Methods, headers and status codes work the same. Only the layers underneath changed.

Practice exercises

Answer first, then use the hints.

Exercise 1 · Warm-upA satellite link

A satellite internet connection has a 600 ms round trip. Here is the TCP and TLS example on that line. What does it print?

Starter codePop out in the code editor (opens in a new tab)JavaScript
const roundTripMs = 600; // a satellite link's round trip
const tcp = 1; // TCP says hello
const tls = 1; // TLS 1.3 agrees on a secret code

const wait = (tcp + tls) * roundTripMs;
console.log(`Over TCP and TLS, the request waits ${wait} ms.`);

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

    Exercise 2 · Warm-upAnother lost packet

    This time packet 2, part of the script, is lost. What does the first line print?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const roundTripMs = 100;
    const lost = 2;
    const files = { CSS: [1, 4], script: [2, 5], picture: [3, 6] };
    const arrives = (packet) => packet * 10 + (packet === lost ? roundTripMs : 0);
    
    for (const [file, packets] of Object.entries(files)) {
      const last = Math.max(...packets);
      // TCP: every packet up to the file's last one must be in.
      const tcp = Math.max(...[1, 2, 3, 4, 5, 6].slice(0, last).map(arrives));
      // QUIC: only the file's own packets must be in.
      const quic = Math.max(...packets.map(arrives));
      console.log(`The ${file} is ready at ${tcp} ms over TCP, ${quic} ms over QUIC.`);
    }

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

      Exercise 3 · PracticeAn HTTPS record

      completefrontend.com adds a DNS HTTPS record with alpn=h3,h2, so line 2 changes. What does the example print now?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      // What completefrontend.com tells browsers about HTTP/3.
      const site = { altSvc: true, httpsRecord: true };
      
      // An HTTPS record arrives before the first connection opens.
      const firstVisit = site.httpsRecord ? "HTTP/3" : "HTTP/2";
      // Alt-Svc arrives with the first response, in time for the next connection.
      const laterVisits = site.altSvc || site.httpsRecord ? "HTTP/3" : "HTTP/2";
      
      console.log(`The first visit comes over ${firstVisit}, later visits over ${laterVisits}.`);

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

        Exercise 4 · PracticeA slow line

        On a line with a 300 ms round trip, packet 3 is lost. When is the script ready over TCP, in ms?

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

          Exercise 5 · ChallengeTaking HTTP/3 back

          Your site moves to a server without HTTP/3. Browsers remember your old Alt-Svc header for a day. What value of the header tells them to forget it now?

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

            Exercise 6 · ChallengeChecking the new address

            When Asha's phone shows up at a new address, the server checks that it can really reach her there. RFC 9000 has a name for this check. What is it?

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

              Check your understanding

              For each question, ask which part of QUIC it's about: streams, the handshake, migration, or discovery.

              The HTTP/3 quiz · 8 questionsScore: first tries count
              1. Question 1 of 8What does the lost-packet model print on its second line?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const roundTripMs = 100;
                const lost = 1;
                const files = { CSS: [1, 4], script: [2, 5], picture: [3, 6] };
                const arrives = (packet) => packet * 10 + (packet === lost ? roundTripMs : 0);
                
                for (const [file, packets] of Object.entries(files)) {
                  const last = Math.max(...packets);
                  // TCP: every packet up to the file's last one must be in.
                  const tcp = Math.max(...[1, 2, 3, 4, 5, 6].slice(0, last).map(arrives));
                  // QUIC: only the file's own packets must be in.
                  const quic = Math.max(...packets.map(arrives));
                  console.log(`The ${file} is ready at ${tcp} ms over TCP, ${quic} ms over QUIC.`);
                }

                Choose an answer to see the explanation.

              2. Question 2 of 8Why does one lost packet hold up every HTTP/2 file on a connection?

                Choose an answer to see the explanation.

              3. Question 3 of 8How many round trips does a new QUIC connection wait before the first request?

                Choose an answer to see the explanation.

              4. Question 4 of 8In the recorded visits to this course's site, why did the first visit use the older protocol?

                Choose an answer to see the explanation.

              5. Question 5 of 8What does connection migration let a phone do?

                Choose an answer to see the explanation.

              6. Question 6 of 8What does the discovery example print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                // What completefrontend.com tells browsers about HTTP/3.
                const site = { altSvc: true, httpsRecord: false };
                
                // An HTTPS record arrives before the first connection opens.
                const firstVisit = site.httpsRecord ? "HTTP/3" : "HTTP/2";
                // Alt-Svc arrives with the first response, in time for the next connection.
                const laterVisits = site.altSvc || site.httpsRecord ? "HTTP/3" : "HTTP/2";
                
                console.log(`The first visit comes over ${firstVisit}, later visits over ${laterVisits}.`);

                Choose an answer to see the explanation.

              7. Question 7 of 8A network blocks UDP. What should a browser do on a site that speaks HTTP/3?

                Choose an answer to see the explanation.

              8. Question 8 of 8Why can comparing HTTP/3 visits with HTTP/2 visits in field data mislead you?

                Choose an answer to see the explanation.

              Key takeaways

              • HTTP/3 is the same HTTP, carried by QUIC instead of TCP and TLS. QUIC runs over UDP and has TLS 1.3 built in.
              • Over TCP, one lost packet holds up every file on the connection. Over QUIC, it holds up only its own stream.
              • A new QUIC connection is ready after one round trip, not two. The recordings saved about one round trip each time.
              • QUIC knows a connection by its connection IDs, so the connection can survive a change of network.
              • Browsers use HTTP/3 only once it's announced. Alt-Svc helps from the second connection, an HTTPS record from the first.
              • Record the protocol with your field data, and compare groups chosen at random.

              Remember the one-liner.
              Same HTTP, new courier: one lost packet holds up one stream, one round trip connects, and browsers must be told before they use it.

              Sources

              The facts on this page come from these standards, docs and studies:

              Coming next: The navigation timeline. You've now followed the first visit through DNS, TCP, TLS and HTTP. The next lesson puts a stopwatch on all of it, from the address bar to the first paint.

              CompleteFrontend Clear concepts. Working examples.