The Application Layer
HTTP/2, HTTP/3 and Performance
A slow response blocks every request queued
JrCodex·7 min read
Jr Codex Computer Networks Notes
Level: Intermediate–Advanced Prerequisites: Chapter 2: HTTP Fundamentals Time to complete: ~20 minutes
Table of Contents
- What Was Wrong With HTTP/1.1
- HTTP/2
- The Problem HTTP/2 Could Not Solve
- HTTP/3 and QUIC
- Optimisations That Became Anti-Patterns
- What Actually Helps
- Summary & Next Steps
1. What Was Wrong With HTTP/1.1
Four Structural Problems
─────────────────────────────────────────
1. ONE REQUEST AT A TIME PER CONNECTION
A response must complete before the next
request may be sent on that connection.
2. HEAD-OF-LINE BLOCKING at the application
layer
A slow response blocks every request queued
behind it.
3. THE SIX-CONNECTION WORKAROUND
Browsers opened ~6 parallel connections per
origin. Each pays its own TCP handshake, TLS
handshake and slow start (Module 4).
4. REPEATED HEADERS
Every request resends Cookie, User-Agent,
Accept — often 500-800 bytes, uncompressed,
nearly identical every time.
─────────────────────────────────────────
Why Pipelining Failed
─────────────────────────────────────────
HTTP/1.1 defined PIPELINING: send several
requests without waiting.
But responses had to return IN ORDER, so
head-of-line blocking remained. And many proxies
implemented it incorrectly.
It was effectively never enabled by default in
any browser. A specified feature that the
ecosystem could not deploy — which is the same
ossification problem QUIC was designed around
(Module 4, Chapter 5).
─────────────────────────────────────────
2. HTTP/2
The Four Changes
─────────────────────────────────────────
BINARY FRAMING
The message is split into typed frames on a
single connection, rather than being text.
Efficient to parse, unambiguous to frame.
MULTIPLEXING
Many concurrent STREAMS on ONE connection.
Requests and responses interleave freely, in
any order.
── this fixes problems 1, 2 and 3 at once
HEADER COMPRESSION (HPACK)
A shared dynamic table on both ends. Repeated
headers become a small index reference.
Typical saving: 80-90% of header bytes.
SERVER PUSH
The server sends resources before they are
requested.
── since removed; see Section 5
─────────────────────────────────────────
The Frame Layout
─────────────────────────────────────────
Connection
└─ Stream 1 ── HEADERS, DATA, DATA
└─ Stream 3 ── HEADERS, DATA
└─ Stream 5 ── HEADERS, DATA, DATA, DATA
Frames from different streams INTERLEAVE on the
wire:
[S1 HDR][S3 HDR][S1 DATA][S5 HDR][S3 DATA]
[S1 DATA][S5 DATA]...
Each frame carries its stream ID, so the receiver
reassembles them independently.
─────────────────────────────────────────
curl -I --http2 https://example.com # negotiate HTTP/2
curl -w "%{http_version}\n" -o /dev/null -s https://example.comWhy One Connection Is Better
─────────────────────────────────────────
Six connections meant six slow starts (Module 4,
Chapter 4), six congestion windows each probing
independently, and six sets of handshakes.
One connection warms up once and stays warm, and
its congestion control sees the true aggregate
rate rather than six flows competing with each
other.
Better for the network AND for the client.
─────────────────────────────────────────
3. The Problem HTTP/2 Could Not Solve
TRANSPORT-LAYER HEAD-OF-LINE BLOCKING
─────────────────────────────────────────
HTTP/2 removed head-of-line blocking at the
APPLICATION layer. It runs on TCP, which has its
own, at the TRANSPORT layer (Module 4,
Chapter 5).
TCP delivers ONE ordered byte stream. Lose one
segment and TCP withholds EVERYTHING after it —
including frames belonging to entirely different
HTTP/2 streams that arrived perfectly.
So one lost packet stalls ALL concurrent
requests.
─────────────────────────────────────────
The Uncomfortable Consequence
─────────────────────────────────────────
On a LOSSY network, HTTP/2's single connection
can be SLOWER than HTTP/1.1's six.
With six connections, a loss stalls one of them
and five keep working. With one, everything
stops.
HTTP/2 is a clear win on good networks and can be
a regression on bad ones — and mobile networks
are frequently bad ones.
Fixing this required replacing TCP.
─────────────────────────────────────────
4. HTTP/3 and QUIC
The Change
─────────────────────────────────────────
HTTP/3 is HTTP/2's semantics over QUIC
(Module 4, Chapter 5) instead of TCP.
QUIC provides INDEPENDENT STREAMS at the
transport layer, so a lost packet affects only
the stream it belonged to.
That is the whole point. Everything else QUIC
brings is a bonus.
─────────────────────────────────────────
What HTTP/3 Gains
─────────────────────────────────────────
NO HEAD-OF-LINE BLOCKING
at either layer. The problem is finally solved.
FASTER CONNECTION SETUP
TCP + TLS = 2-3 round trips.
QUIC = 1, or 0 when resuming.
On a 100ms path that is 200-300ms saved on
every new connection.
CONNECTION MIGRATION
Switching from wifi to mobile keeps the
connection alive — it is identified by a
connection ID, not the 4-tuple.
QPACK
Header compression redesigned so that
out-of-order stream delivery does not create a
new dependency between streams.
─────────────────────────────────────────
Negotiation, and Why It Is Invisible
─────────────────────────────────────────
A server advertises HTTP/3 with the Alt-Svc
header on an HTTP/2 response:
Alt-Svc: h3=":443"; ma=86400
The client connects over TCP first, sees the
header, and uses QUIC for subsequent
connections.
If UDP is blocked, it silently stays on HTTP/2.
Users never see a failure — the same
race-and-fall-back pattern as Happy Eyeballs
(Module 3, Chapter 2).
─────────────────────────────────────────
5. Optimisations That Became Anti-Patterns
Techniques That HTTP/2 Made HARMFUL
─────────────────────────────────────────
DOMAIN SHARDING
Serving assets from cdn1, cdn2, cdn3 to get
more parallel HTTP/1.1 connections.
NOW HARMFUL: each domain is another DNS lookup,
another TCP and TLS handshake, another
congestion window — and multiplexing already
solved the parallelism problem.
CONCATENATING FILES
Bundling all JavaScript into one file to reduce
request count.
NOW COUNTERPRODUCTIVE: requests are cheap, and
one changed line invalidates the entire bundle's
cache. Smaller files cache far better
(Chapter 2's hashed filenames).
IMAGE SPRITES
Same reasoning. Same conclusion.
INLINING ASSETS
Embedding CSS or images in the HTML to save a
request. Now it just makes the HTML
uncacheable.
─────────────────────────────────────────
SERVER PUSH — a cautionary tale
─────────────────────────────────────────
HTTP/2's most-publicised feature. The server
sends resources before the client asks.
IT WAS REMOVED FROM BROWSERS.
WHY: the server cannot know what the client
already has cached, so it frequently pushed
bytes that were discarded — wasting bandwidth and
competing with resources actually needed.
REPLACED BY:
<link rel="preload"> — the client decides, and
it knows its own cache
103 Early Hints — the server SUGGESTS, the
client chooses
The lesson generalises: the endpoint with the
information should make the decision.
─────────────────────────────────────────
6. What Actually Helps
Ordered by Impact
─────────────────────────────────────────
1. REDUCE ROUND TRIPS
Latency dominates page load (Module 1,
Chapter 5). Every eliminated round trip is
worth more than any bandwidth optimisation.
- keep connections alive
- HTTP/3 for 0-RTT resumption
- avoid redirect chains (each is a full
round trip)
2. REDUCE DISTANCE
A CDN attacks propagation delay, which is the
one component nothing else can touch
(Module 7, Chapter 2).
3. CACHE PROPERLY
A request not made costs nothing. Chapter 2's
hashed-filename pattern.
4. COMPRESS
Brotli beats gzip by 15-25% on text. Both are
essentially free.
5. SEND LESS
Unused JavaScript is the largest avoidable
cost on most sites. Ship less code.
6. PRIORITISE CORRECTLY
Load what renders the page first; defer the
rest.
─────────────────────────────────────────
# Where does the time actually go? (Module 1, Chapter 5's breakdown.)
curl -w "dns:%{time_namelookup} connect:%{time_connect} \
tls:%{time_appconnect} ttfb:%{time_starttransfer} total:%{time_total} \
proto:%{http_version}\n" -o /dev/null -s https://example.comThe Discipline
─────────────────────────────────────────
Measure before optimising, and measure on a
REALISTIC connection.
Everything looks fast on a 1 Gbps office link
with 5ms latency. Test with throttling that
matches your users — mobile latency is 50-200ms,
and that is where round trips hurt.
Most sites are slow because of round trips and
payload size, not because of protocol version.
─────────────────────────────────────────
7. Summary & Next Steps
Key Takeaways
- HTTP/1.1 allowed one outstanding request per connection, which browsers worked around with six connections, each paying its own handshakes and slow start.
- HTTP/2's multiplexing fixed application-layer head-of-line blocking but inherited TCP's, so one lost packet stalls every concurrent request.
- HTTP/3 over QUIC gives independent transport streams, one-round-trip setup and connection migration — solving the problem HTTP/2 structurally could not.
- Sharding, bundling and sprites were HTTP/1.1 workarounds that are now counterproductive, and server push failed because the server cannot know the client's cache.
Concept Check
- Why can HTTP/2 be slower than HTTP/1.1 on a lossy mobile network?
- Why did concatenating all JavaScript into one bundle stop being a good idea?
- Why was server push removed, and what replaced it?
Next Chapter
→ Chapter 4: REST, WebSockets and Real-Time
Jr Codex — 1-on-1 Personalized Coaching | Back to Module Index | Back to Computer Networks Index