TCP connections and slow start
How TCP connects, starts small and grows: the handshake, slow start and the first 14 KB, what a lost packet costs, and why reused connections win.
- 01Count a connection's round tripsSay why a new connection waits one round trip before the request, and work out how many round trips slow start needs for a file.
- 02Use the first 14 KB wellSay where the 14 KB comes from, what counts against it, and why it's a rough guide rather than a hard limit.
- 03Reason about loss and reuseDescribe what a lost packet does to the window and to the data behind it, and why a connection kept open saves time.
A connection before the first byte
DNS lookups ended with the browser holding a number: 199.36.158.100, the address behind completefrontend.com. So now it can ask for the page, right?
Not yet. Here are two times the course recorded on its first visit to completefrontend.com:
// The course's first visit to completefrontend.com, in ms from the start.const visit = { domainLookupEnd: 26, requestStart: 83.9 }; console.log(`The browser knew the address at ${visit.domainLookupEnd} ms.`);console.log(`It asked for the page at ${visit.requestStart} ms.`);- Line 1 is a comment. Each time is in milliseconds (ms), counted from the moment the visit started. A millisecond is a thousandth of a second.
- Line 2 holds two recorded times. The names come from the browser's own stopwatch, which The navigation timeline reads in full.
- Line 4 prints
The browser knew the address at 26 ms. - Line 5 prints
It asked for the page at 83.9 ms.
For almost 58 ms, the browser had the address and still didn't ask. What was it waiting for? It was opening a connection to the server. On this site, that takes two steps. The first is TCP, and it's what this lesson is about. The second is TLS, the step that makes the connection private. That's the next lesson.
Here is that visit's connection, drawn to scale. Time goes down:
A trip across and back is a round trip. The hello took one. The page took about three more, because it came back in batches.
TCP, the Transmission Control Protocol, is the set of rules that carries the page from the server to the browser. Why does a page need rules to travel? Because the network only moves small chunks of data, called packets. And it moves them carelessly. It can lose a packet, deliver one twice, or deliver them out of order.
TCP fixes all of that. It cuts the page into numbered pieces and checks that each one arrives. It sends again whatever goes missing. And it hands the pieces to the browser in the right order. A long letter sent by post needs the same care:
You post a long letter to Ravi, a few pages at a time, and you number every page. Each time some arrive, Ravi texts you: "Got pages 1 to 10." If a page goes missing, you post it again. And Ravi reads the pages in order, even when they arrive mixed up.
- In real life: Each numbered page
- In JavaScript: A segment: one piece of the page, about 1,460 bytes
- In real life: Ravi's text: "Got pages 1 to 10"
- In JavaScript: An acknowledgment, or ACK
- In real life: Posting a missing page again
- In JavaScript: A retransmission
- In real life: Reading the pages in order, however they arrive
- In JavaScript: TCP handing the bytes to the browser in order
TCP has names for these. A segment is one numbered piece of the page. It holds about 1,460 bytes, and it travels inside a packet. An acknowledgment, or ACK, is the short note the browser sends back to say which segments arrived.
All this care keeps the page whole. But it costs time, in three ways:
- The handshake. Before any data moves, the browser and the server say hello. That takes one round trip.
- Slow start. A new connection sends only a little at first, then more each round trip.
- Loss. A lost packet slows the connection down, and holds up everything behind it.
A round trip is the time it takes a message to reach the server and an answer to come back. Latency vs bandwidth showed that round trips, not megabits, decide how fast most pages load. That's why the picture at the top counts them. The next two sections take it apart: first the hello, then the batches.
A new TCP connection spends one round trip saying hello. Then it sends about 14 KB, and doubles that every round trip. A lost packet cuts the speed in half and holds up what comes after it. So keep connections open and reuse them. And put what the page needs first into the first 14 KB.
The three-way handshake
Before a single byte of the page can travel, the browser and the server greet each other. It works like a phone call:
You phone the tea shop, Leaf and Kettle, to order. You don't start your order the moment you dial. You wait until someone picks up: "Hello, Leaf and Kettle?" You say "Hi!", and then you order straight away. Nothing useful has been said yet, but the call has already taken a few seconds.
- In real life: Dialling the tea shop
- In JavaScript: SYN: the browser asks to open a connection
- In real life: "Hello, Leaf and Kettle?"
- In JavaScript: SYN-ACK: the server agrees
- In real life: "Hi!", then your order straight away
- In JavaScript: ACK, with the request right behind it
- In real life: The seconds before your order
- In JavaScript: One round trip before the request can leave
TCP opens a connection with three messages, so it's called the three-way handshake:
- SYN, short for synchronize. The browser asks the server to open a connection.
- SYN-ACK. The server agrees, and acknowledges the browser's SYN.
- ACK. The browser acknowledges the server's answer.
Here's the part that saves time. The browser doesn't wait after its ACK. It sends the request right behind it. So three messages cost only one round trip of waiting: the SYN going out, and the SYN-ACK coming back. In the picture at the top, that's the first round trip: hello? is the SYN, and hello! is the SYN-ACK.
On an HTTPS site like this one, TLS's first message takes the request's place behind the ACK. The request follows once TLS is done.
How long is that round trip? Back to the course's first visit:
// The same visit: when TCP started, and when TLS took over.const visit = { connectStart: 26, secureConnectionStart: 45.6 }; const handshakeMs = visit.secureConnectionStart - visit.connectStart;console.log(`The TCP handshake took ${handshakeMs.toFixed(1)} ms.`);- Line 2 holds two recorded times.
connectStartis when the browser started connecting.secureConnectionStartis when TCP was done and TLS began. - Line 4 subtracts the first from the second.
- Line 5 prints
The TCP handshake took 19.6 ms.
That's one round trip to completefrontend.com's server. The rest of the connection, 38.2 ms, went on TLS. So that answers the question from the start: of the almost 58 ms, TCP took 19.6 and TLS took 38.2.
A round trip gets longer with distance. Ilya Grigorik works out an example in his book, High Performance Browser Networking. Between New York and London, light in an optical fibre needs at least 56 ms to go there and back. So a handshake between those two cities takes at least 56 ms, however fast the line is.
Why it matters: no amount of bandwidth makes the handshake shorter. The only way to skip it is to not need a new connection. The section on reuse comes back to this.
One TCP feature trims it. TCP Fast Open lets a repeat connection carry data in its very first message. It has limits, though: on how much data, and on which requests.
Congestion window and slow start
The hello is done, and the request has left. Now the server sends the page. Say the page is 50 KB, and your line is fast. Does the whole page arrive in the next round trip?
No. The server doesn't know how fast your line is. It doesn't know how busy the network in between is, either. If it sends too much at once, the routers on the way, the machines that pass packets along, drop what they can't hold. So it starts small, and learns as it goes, like a new waiter:
A café hires a new waiter. On their first trip to the tables, they carry only a few plates. They don't know the room yet, or how crowded it is. Each trip that goes well, they carry twice as many. After a few trips, they carry a full tray.
- In real life: The plates on one trip
- In JavaScript: The congestion window: how much the server may send before it waits for ACKs
- In real life: One trip to the tables and back
- In JavaScript: One round trip
- In real life: Twice as many plates after each good trip
- In JavaScript: Slow start: the window doubles each round trip
- In real life: The few plates on the very first trip
- In JavaScript: The initial window: 10 segments, about 14 KB
In TCP, the plates on one trip are the congestion window. It's how much the server may send before it stops and waits for ACKs. Congestion means a crowded network, like a crowded road.
The window grows as ACKs come back. For each segment acknowledged, it grows by one segment. In one round trip, every segment sent gets its ACK. So each round trip, the window doubles. This first phase is called slow start. The name is odd, because the window grows fast. It's the start that's small.
Where does it start? Since 2013, a new connection may start with 10 segments, at most 14,600 bytes. That's about 14 KB. The number comes from RFC 6928. RFCs are the numbered documents the internet's rules are written in. The rule before it allowed 2 to 4 segments. This first size is called the initial window. Here's the doubling, one round trip at a time:
// A new connection may send 10 segments before it waits.let windowSize = 10; for (let trip = 1; trip <= 4; trip++) { console.log(`Round trip ${trip}: ${windowSize} segments, ${windowSize * 1460} bytes`); windowSize = windowSize * 2;}- Line 1 is a comment. Line 2 starts the window at 10 segments.
- Line 4 repeats for four round trips.
- Line 5 prints the window, in segments and in bytes. Each segment holds 1,460 bytes.
- Line 6 doubles the window for the next round trip.
- It prints
Round trip 1: 10 segments, 14600 bytesandRound trip 2: 20 segments, 29200 bytes. ThenRound trip 3: 40 segments, 58400 bytesandRound trip 4: 80 segments, 116800 bytes.
So how many round trips does the 50 KB page take? Keep sending windows until the whole file is out:
const fileBytes = 50000;let windowSize = 10;let sent = 0;let trips = 0;while (sent < fileBytes) { sent += windowSize * 1460; windowSize *= 2; trips++;}console.log(`A ${fileBytes / 1000} KB file takes ${trips} round trips.`);- Line 1 is the file: 50,000 bytes, or 50 KB.
- Line 2 starts the window at 10 segments. Lines 3 and 4 count the bytes sent and the round trips.
- Line 5 repeats until the whole file is sent.
- Line 6 sends one window. Line 7 doubles it. Line 8 counts one more round trip.
- Line 10 prints
A 50 KB file takes 3 round trips.
Two round trips carry 14.6 + 29.2 = 43.8 KB. That's not quite 50, so a third is needed. Add the round trip for the hello, and the last byte arrives four round trips after the browser started connecting. That's before TLS adds its own.
Why it matters: a faster line doesn't change that count. On a new connection, the window decides how much may go in each round trip, not the line.
The window doesn't double forever. It stops doubling when one of these happens:
- It reaches the browser's limit. The browser tells the server how much it can take at once. That's the receive window. The server never sends more than it, however big the congestion window gets.
- It reaches a threshold. TCP can set a size where slow start ends. After it, the window grows by only one segment per round trip.
- A packet is lost. The section on loss shows what happens then.
Does a bigger start really help? Google tested it on its search pages, and RFC 6928 reports the results. On average, search results arrived 11.7% sooner in one data center, and 8.7% sooner in another. On paths with long round trips, the gain was over 20%. Saving a round trip matters most where round trips are long.
Try it yourself. Pick a file size and a starting window. The third button loses a packet in round trip 3. The section on loss explains what happens then.
let window = initialSegments;let slowStart = true;while (delivered < needed) { const lost = round === lossAt; send(window); delivered += lost ? window - 1 : window; if (lost) slowStart = false; window = lost ? window / 2 : slowStart ? window * 2 : window + 1;}10 (14.6 KB)20 (29.2 KB)5 (7.3 KB)A 50 KB file, starting with 10 segments, takes 3 round trips. At 100 ms each, that's about 300 ms, not counting the handshake.
In the panel's code:
- Line 6 counts what got through. A lost segment doesn't count, because it has to be sent again.
- Line 8 sets the next window. It doubles in slow start. After a loss, it halves, then grows by one segment per round trip.
The 14 KB rule and its limits
The first round trip of data carries about 14 KB. That gives web developers a rough guide, often called the 14 KB rule. Put what the browser needs to start showing the page into the first 14 KB. Then it can start building the page after one round trip of data, instead of waiting for more.
It's the new waiter's first trip again. Bring the drinks and the bread first, so the table can start while the main course is on its way.
What does the page need first? The top of the HTML, and the CSS for the first screen, often written inside the HTML. Here's a quick check for one page:
const firstRoundTrip = 10 * 1460;const headers = 600;const html = 9000;const inlineCss = 4000;const start = headers + html + inlineCss; console.log(`The first round trip carries ${firstRoundTrip} bytes.`);console.log(`This page's start is ${start} bytes, so ${start <= firstRoundTrip ? "it fits" : "it needs another round trip"}.`);- Line 1 is the first round trip: 10 segments of 1,460 bytes.
- Lines 2 to 4 are the parts of this page's start: the response's headers, the HTML, and the CSS written inside it.
- Line 5 adds them up.
- Line 7 prints
The first round trip carries 14600 bytes. - Line 8 prints
This page's start is 13600 bytes, so it fits.
Why it matters: when the start fits, the browser has everything the first screen needs after one round trip of data. When it doesn't, part of it comes a round trip later.
Don't count to the byte, though. It's a rough guide, not a law. Read this table across: each row is something that changes the sum, and how it changes it.
| What changes | How it changes the sum |
|---|---|
| Compression | The window counts the bytes that travel. So count the compressed size, not the size of the file on disk. |
| Headers | The response's headers travel in the same segments as the HTML. They count too. |
| HTTPS | On a new HTTPS connection, the server first sends its certificates. They travel under the same window, before any HTML. |
| A bigger start | Some servers start with more than 10 segments. A reused connection's window has already grown. |
So keep the start of the HTML small and compressed. Leave out anything the first screen doesn't need. Later stages of the course show how.
Packet loss and retransmission
So far, every packet arrived. On a real network, some don't. A router with a full queue drops what it can't hold. A crowded café's Wi-Fi loses some too.
How does TCP notice? The ACKs tell it. Say the browser's ACKs keep saying "I have everything up to segment 2", while later segments arrive. Then segment 3 is missing. And if no ACK comes back for too long, something was lost too.
Then TCP does two things. It sends the missing segment again. That's a retransmission. And it slows down, because it reads the loss as a sign that the network is too full. Think of a garden hose:
You water the garden with a hose you haven't used before. You open the tap a little, then more and more, until water sprays from a joint. You turn the tap back to half. Then you open it again, but slowly, a little at a time.
- In real life: Opening the tap more and more
- In JavaScript: Slow start: the window doubles each round trip
- In real life: Water spraying from a joint
- In JavaScript: A lost packet
- In real life: Turning the tap back to half
- In JavaScript: The window halves
- In real life: Opening it again, a little at a time
- In JavaScript: The window grows by one segment per round trip
That's how classic TCP recovers. Grigorik's book calls it AIMD, for multiplicative decrease and additive increase. In plain words: cut the window in half after a loss, then add one segment at a time. Here it is, with a loss in round trip 3:
const lostIn = 3;let windowSize = 10;const sizes = [];for (let trip = 1; trip <= 5; trip++) { sizes.push(windowSize); if (trip < lostIn) windowSize *= 2; else if (trip === lostIn) windowSize /= 2; else windowSize += 1;}console.log(`A loss in round trip ${lostIn}: ${sizes.join(", ")} segments.`);- Line 1 says when the loss happens: in round trip 3.
- Line 2 starts the window at 10 segments. Line 3 is a list for the window of each round trip.
- Line 4 repeats for five round trips. Line 5 writes down this round trip's window.
- Line 6 doubles the window before the loss.
- Line 7 halves it after the round trip that had the loss.
- Line 8 adds one segment in every round trip after it.
- Line 10 prints
A loss in round trip 3: 10, 20, 40, 20, 21 segments.
Without the loss, round trip 5 would carry 160 segments. With it, only 21. So one lost packet early in a page load costs many round trips later.
Newer methods get back up to speed faster. One of them, CUBIC, is the default in Linux, Windows and Apple's systems, RFC 9438 says. But a loss still costs time.
One loss holds up everything behind it
A loss has a second cost. Remember Ravi reading the letter in order. TCP hands data to the browser in order too. So when segment 3 is lost, segments 4, 5 and 6 can arrive, but the browser doesn't get them yet. They wait in TCP's buffer, a waiting area in memory, until a new copy of segment 3 arrives.
This is called head-of-line blocking. It's like the queue at the tea shop's counter. When the person at the front can't find their purse, nobody behind them gets served.
Why it matters: HTTP/2 sends many files over one TCP connection. HTTP/2 is a version of HTTP, the language browsers and servers speak, and this site's first visit used it. But TCP doesn't know about files. It only sees bytes in order. So one lost packet stalls every file on the connection, even the ones it has nothing to do with. HTTP/3 solves this, and HTTP/3 and QUIC shows how.
Keeping connections alive and reused
Every new connection pays twice. It spends a round trip saying hello, and it starts with a small window that has to grow. A connection that stays open pays neither again.
You have six questions for the tea shop. You could ask one, hang up, then dial again for the next. Every call would start with the ringing and the "Hello?". Or you could stay on the line and ask all six. By the third question, you're both talking at full speed.
- In real life: Hanging up and dialling again
- In JavaScript: A new connection for each request: a handshake every time
- In real life: Staying on the line
- In JavaScript: Keep-alive: one connection for many requests
- In real life: Talking at full speed by the third question
- In JavaScript: A window that has already grown
HTTP/1.1 keeps a connection open after a response, unless told otherwise. This is called keep-alive, or a persistent connection. The next request skips the handshake, and finds a window that has already grown. Here's what the hello alone costs six requests, with each round trip at 100 ms:
const roundTripMs = 100;const requests = 6; const newEachTime = requests * 2 * roundTripMs;const keptAlive = 2 * roundTripMs + (requests - 1) * roundTripMs; console.log(`A new connection for each request: ${newEachTime} ms.`);console.log(`One connection, kept alive: ${keptAlive} ms.`);console.log(`Keep-alive saved ${newEachTime - keptAlive} ms.`);- Line 1 sets one round trip to 100 ms. Line 2 is the number of requests.
- Line 4 gives each request its own connection. Each one takes two round trips: one for the hello, one for the request and its answer.
- Line 5 keeps one connection open. The first request takes two round trips, and each of the other five takes one.
- Lines 7 and 8 print
A new connection for each request: 1200 ms.andOne connection, kept alive: 700 ms. - Line 9 prints
Keep-alive saved 500 ms.
Grigorik puts the saving at (N − 1) × RTT for N requests. RTT is short for round trip time. In words: one round trip for every request after the first. And this sum leaves out slow start, which a kept connection also skips.
How many connections does a browser open? On HTTP/1.1, a connection carries one request at a time. So browsers open up to six connections to each host. Each of them pays its own handshake and its own slow start. HTTP/2 and HTTP/3 send all of a host's files over one connection. HTTP/1.1 and HTTP/2 compares them.
An open connection can also cool down. If it sits idle for a while, TCP may shrink its window back to the start. It's like a waiter back from a long break, who starts again with a few plates. This is called slow-start restart. Grigorik recommends turning it off on servers, so that connections kept open stay fast.
Which of this page's own files reused a connection? Press Look:
const [page] = performance.getEntriesByType("navigation");console.log(page.nextHopProtocol, page.transferSize, "bytes"); for (const file of performance.getEntriesByType("resource")) { const connect = file.connectEnd - file.connectStart; const kind = file.connectStart === 0 ? "hidden" : connect > 0 ? "new connection" : "reused"; count(kind, file.nextHopProtocol);}Nothing read yet.
Press Look. You'll see how this page reached your browser: the protocol, the size of its HTML on the network, and which files needed a new connection.
In the panel's code:
- Line 2 reads the page's protocol, and how many bytes its HTML took on the network.
- Line 5 works out how long each file spent connecting.
- Line 6 sorts the files. No time connecting means the file reused a connection. A zero
connectStartmeans another site hid its timings. A site shares them only if it sends aTiming-Allow-Originheader.
What is it costing?
Each delay below comes mostly from one of TCP's three costs. For each one, ask: is the page waiting to say hello, waiting for the window to grow, or waiting for a lost packet?
- A font from a host the page hasn't connected to yet
- A server that closes the connection after every response
- A 500 KB file on a brand-new connection
- HTML that's 40 KB compressed, on a first visit
- A connection that sat idle, then sends a large response
- Crowded café Wi-Fi that drops packets
- Every file on an HTTP/2 connection stalls while one packet is sent again
Sort each delay by the TCP cost behind it.
Common mix-ups
- "Bandwidth decides how fast a big file starts." No. A new connection sends about 14 KB in its first round trip, however fast the line is. The window grows from there.
- "The 14 KB rule means pages must be under 14 KB." No. It's about what the first round trip carries. The rest of the page just takes more round trips.
- "The handshake takes three round trips." No. It's three messages, but one round trip. The request leaves right behind the last one.
- "Packet loss only delays the lost packet." No. It halves the window. And everything behind the lost packet waits for it.
- "More connections are always faster." No. Each new one pays its own handshake and slow start, and they all share the same line.
- "A connection's speed is set when it opens." No. It grows in slow start, shrinks after a loss, and can start over after sitting idle.
Practice exercises
Answer first, then use the hints.
Servers used to start with 4 segments. In this copy of the round trips example, line 2 starts with 4. What does it print?
const fileBytes = 50000;
let windowSize = 4;
let sent = 0;
let trips = 0;
while (sent < fileBytes) {
sent += windowSize * 1460;
windowSize *= 2;
trips++;
}
console.log(`A ${fileBytes / 1000} KB file takes ${trips} round trips.`);A 50 KB file takes 4 round trips. That's one more than with a 10-segment start. The smaller first window costs a whole round trip.
The page now makes ten requests. What does the last line print?
const roundTripMs = 100;
const requests = 10;
const newEachTime = requests * 2 * roundTripMs;
const keptAlive = 2 * roundTripMs + (requests - 1) * roundTripMs;
console.log(`A new connection for each request: ${newEachTime} ms.`);
console.log(`One connection, kept alive: ${keptAlive} ms.`);
console.log(`Keep-alive saved ${newEachTime - keptAlive} ms.`);Keep-alive saved 900 ms. That's (10 − 1) × 100 ms: one hello saved for every request after the first.
The HTML grows to 12,000 bytes. What does the last line print?
const firstRoundTrip = 10 * 1460;
const headers = 600;
const html = 12000;
const inlineCss = 4000;
const start = headers + html + inlineCss;
console.log(`The first round trip carries ${firstRoundTrip} bytes.`);
console.log(`This page's start is ${start} bytes, so ${start <= firstRoundTrip ? "it fits" : "it needs another round trip"}.`);This page's start is 16600 bytes, so it needs another round trip. It's 2,000 bytes over. Moving CSS the first screen doesn't need out of the HTML, or trimming the HTML, would bring it back under.
This time, the packet is lost in round trip 2. What does it print?
const lostIn = 2;
let windowSize = 10;
const sizes = [];
for (let trip = 1; trip <= 5; trip++) {
sizes.push(windowSize);
if (trip < lostIn) windowSize *= 2;
else if (trip === lostIn) windowSize /= 2;
else windowSize += 1;
}
console.log(`A loss in round trip ${lostIn}: ${sizes.join(", ")} segments.`);A loss in round trip 2: 10, 20, 10, 11, 12 segments. The earlier the loss, the smaller the window it leaves. By round trip 5, it's 12 segments instead of 160.
Open DevTools' Network panel and reload a page. Click a request that needed a new connection, and open its Timing tab. What is the phase for the TCP handshake called?
Initial connection: the TCP handshake. For HTTPS, it also includes TLS, which DevTools shows again on a row of its own, SSL.
In the connections playground, why do most of this site's own files show a reused connection?
They share one connection. This site's files come from the same host, over a connection that's already open. So they skip the hello and the small start.
Check your understanding
For each question, ask which cost it's about: the hello, the growing window, or a lost packet.
Question 1 of 7This is the round trips example with a 200 KB file. What does it print?
Read the code, then predictconst fileBytes = 200000; let windowSize = 10; let sent = 0; let trips = 0; while (sent < fileBytes) { sent += windowSize * 1460; windowSize *= 2; trips++; } console.log(`A ${fileBytes / 1000} KB file takes ${trips} round trips.`);Choose an answer to see the explanation.
Question 2 of 7How long does TCP's three-way handshake hold up the first request?
Choose an answer to see the explanation.
Question 3 of 7Where does the 14 KB in the 14 KB rule come from?
Choose an answer to see the explanation.
Question 4 of 7This is the loss example with the loss in round trip 4. What does it print?
Read the code, then predictconst lostIn = 4; let windowSize = 10; const sizes = []; for (let trip = 1; trip <= 5; trip++) { sizes.push(windowSize); if (trip < lostIn) windowSize *= 2; else if (trip === lostIn) windowSize /= 2; else windowSize += 1; } console.log(`A loss in round trip ${lostIn}: ${sizes.join(", ")} segments.`);Choose an answer to see the explanation.
Question 5 of 7What is TCP head-of-line blocking?
Choose an answer to see the explanation.
Question 6 of 7What does the last line of the keep-alive example print?
Read the code, then predictconst roundTripMs = 100; const requests = 6; const newEachTime = requests * 2 * roundTripMs; const keptAlive = 2 * roundTripMs + (requests - 1) * roundTripMs; console.log(`A new connection for each request: ${newEachTime} ms.`); console.log(`One connection, kept alive: ${keptAlive} ms.`); console.log(`Keep-alive saved ${newEachTime - keptAlive} ms.`);Choose an answer to see the explanation.
Question 7 of 7Why does a big file load faster on a connection that's already been in use?
Choose an answer to see the explanation.
Key takeaways
- A new TCP connection spends one round trip saying hello before the request can leave.
- Slow start begins at 10 segments, about 14 KB, and doubles every round trip.
- A faster line doesn't remove those round trips. The window decides how much goes each time.
- Put what the first screen needs into the first 14 KB, counted compressed and with headers.
- A lost packet halves the window, and holds up everything behind it.
- Keep-alive saves a round trip for every request after the first. Idle connections can cool down.
Remember the one-liner.
Connect once, start small, grow fast, and don't lose anything.
Sources
The facts on this page come from these standards, books and docs:
- Navigation Timing Level 2, W3C.
- Building Blocks of TCP, High Performance Browser Networking, Ilya Grigorik.
- RFC 9293: Transmission Control Protocol (TCP), IETF.
- RFC 5681: TCP Congestion Control, IETF.
- RFC 6928: Increasing TCP's Initial Window, IETF.
- RFC 9438: CUBIC for Fast and Long-Distance Networks, IETF.
- HTTP/1.X, High Performance Browser Networking, Ilya Grigorik.
- PerformanceResourceTiming: nextHopProtocol, MDN.
- RequestTimingView.ts: the Network panel's Timing tab, Chrome DevTools source.
Coming next: TLS without the wait. Remember the other 38.2 ms of the first visit's connection? That was TLS, making the line private. TLS 1.3, resumption and good certificates make most of its round trips disappear.