QUIC Design Goals — Why Reinvent TCP?
TCP has been the internet's reliable transport layer since 1974. It is baked into every operating system kernel, every router, and every middlebox on the network. Changing TCP itself is nearly impossible — any modification must be backward compatible with 50 years of deployed equipment.
QUIC (RFC 9000, 2021) started as a Google experiment in 2012 and became an IETF standard in 2021. Its design goals were explicit:
- Eliminate TCP head-of-line blocking — loss on one stream must not stall others
- Reduce connection latency — 1-RTT for new connections, 0-RTT for resumption
- Encrypt everything — no cleartext transport, prevent ossification
- Connection migration — survive IP address changes (mobile networks)
- Userspace implementation — deployable without kernel changes
UDP Substrate
QUIC runs on UDP — not because UDP is better than TCP, but because UDP packets pass through NAT and firewalls without requiring special middlebox support, and because UDP datagrams can be processed in userspace. QUIC reimplements everything TCP provides (reliability, ordering, flow control, congestion control) but does so inside the encrypted payload where network devices cannot interfere.
Network Stack Comparison:
Traditional HTTPS: QUIC + HTTP/3:
┌─────────────┐ ┌─────────────┐
│ HTTP/2 │ │ HTTP/3 │
├─────────────┤ ├─────────────┤
│ TLS 1.3 │ │ QUIC │ ← reliability + crypto combined
├─────────────┤ ├─────────────┤
│ TCP │ │ UDP │
├─────────────┤ ├─────────────┤
│ IP │ │ IP │
└─────────────┘ └─────────────┘
Ossification Prevention
A key QUIC design insight: middleboxes can only ossify what they can see. Because QUIC encrypts almost everything (including most packet headers), network devices cannot inspect or modify QUIC internals. This means QUIC can evolve its transport semantics without being blocked by deployed hardware that expects specific byte patterns.
Connection Establishment
1-RTT Handshake
A QUIC connection combines transport and TLS negotiation into a single handshake:
Client Server
│ │
│──── Initial (ClientHello) ───────────────▶│ RTT 0
│ - QUIC version, connection ID │
│ - TLS ClientHello with key share │
│ │
│◀─── Initial (ServerHello) ───────────────│
│◀─── Handshake (EncryptedExtensions) ────│ RTT 1 complete
│◀─── Handshake (Certificate) ────│
│◀─── Handshake (CertificateVerify) ────│
│◀─── Handshake (Finished) ────│
│ │
│──── Handshake (Finished) ────────────────▶│
│──── 1-RTT (HTTP/3 HEADERS frame) ────────▶│ Application data
│◀─── 1-RTT (HTTP/3 HEADERS + DATA) ───────│
After 1 round-trip, both sides have derived encryption keys and the client can send application data. Contrast with TCP + TLS 1.3 which requires the TCP handshake first (1 RTT) then the TLS handshake (1 more RTT) = 2 RTTs.
0-RTT Resumption
On a subsequent connection to a known server, QUIC can use a Pre-Shared Key stored from the previous session to encrypt the ClientHello AND application data together in the very first packet:
Client Server
│ │
│──── Initial (0-RTT data + ClientHello) ──▶│ Application data sent immediately
│◀─── Handshake + 1-RTT data ──────────────│ Response before RTT completes
The catch: 0-RTT data is not forward-secret (it uses the PSK, not a fresh key exchange) and is vulnerable to replay attacks. RFC 9001 restricts 0-RTT to applications that can tolerate replay — idempotent requests only.
Version Negotiation
QUIC includes version negotiation built into the Initial packet. If the server does not support the client's requested version, it sends a Version Negotiation packet listing supported versions. The current QUIC version is 1 (0x00000001). Version 2 (RFC 9369) shuffles packet type assignments to prevent middlebox ossification of version-1-specific patterns.
Stream Multiplexing — No Head-of-Line Blocking
Stream Types and IDs
QUIC streams are lightweight, independently-delivered byte-streams. Each stream has an ID encoded as a 62-bit integer with two low bits encoding type:
Stream ID encoding:
Bits 63-2: stream number
Bit 1: 0=client-initiated, 1=server-initiated
Bit 0: 0=bidirectional, 1=unidirectional
Examples:
0 (0b00) Client-initiated bidirectional stream 0
1 (0b01) Client-initiated unidirectional stream 0
2 (0b10) Server-initiated bidirectional stream 0
3 (0b11) Server-initiated unidirectional stream 0
4 (0b100) Client-initiated bidirectional stream 1
Independent Stream Delivery
Within a stream, data is delivered in order. Across streams, there is no ordering guarantee. The QUIC stack delivers stream data to the application as soon as it arrives in-order *for that stream*, regardless of what is happening on other streams.
Packet loss scenario:
Packets: [Stream4 chunk1] [Stream8 chunk1] [Stream4 chunk2] ← LOST ← [Stream8 chunk2]
TCP behavior: Stream 8 chunk 2 blocks behind lost Stream 4 chunk 2
QUIC behavior: Stream 8 chunk 2 delivered immediately; only Stream 4 waits
Flow Control
QUIC implements two levels of flow control:
- Stream-level: Each stream has a receive window. The sender cannot exceed the receiver's buffer capacity for that stream.
- Connection-level: The entire connection has an aggregate receive window preventing any one peer from overwhelming the other.
Flow control windows are advertised via MAX_STREAM_DATA and MAX_DATA frames and updated by the receiver as it processes data.
Loss Detection and Recovery
Packet Numbering
Unlike TCP sequence numbers (byte offsets), QUIC uses packet numbers — each packet has a unique, monotonically increasing number. Retransmitted data is sent in a *new* packet with a *new* packet number. This eliminates TCP's retransmission ambiguity (was the ACK for the original or the retransmit?).
TCP retransmission ambiguity:
Send: seq=1000 ← lost
Retransmit: seq=1000 ← same number
ACK for seq=1000 — which transmission caused it?
QUIC:
Send: pn=42 ← lost
Retransmit same data in: pn=47 ← new number
ACK for pn=47 — unambiguous
ACK Frames
QUIC ACK frames acknowledge received packets using ranges, similar to TCP SACK:
ACK frame:
Largest Acknowledged: 150
ACK Delay: 2ms
ACK Ranges: [130-150, 100-125] ← gaps: 126-129 not received
QUIC supports up to 255 ACK ranges per frame, allowing very precise acknowledgment of sparse packet reception — important on lossy networks.
Congestion Control
RFC 9002 specifies QUIC's loss detection and congestion control algorithms, which are similar to modern TCP but implemented in userspace:
- Loss detection: Uses ACK-based loss detection with a packet threshold (3 packets) or a time threshold (9/8 × max RTT)
- Congestion control: QUIC implementations typically use CUBIC (same as Linux TCP) or BBR (Bottleneck Bandwidth and RTT)
- Pacing: QUIC can pace packet transmission in userspace, spreading bursts over time to reduce queue buildup
Because congestion control runs in userspace, QUIC implementations can update their algorithms without OS changes — Google deployed BBRv2 in their QUIC implementation years before it was available in Linux TCP.
Connection Migration
Connection IDs Enable Migration
TCP connections are bound to a 4-tuple (src-IP, src-port, dst-IP, dst-port). Change any element and the connection breaks. QUIC connections are identified by Connection IDs — opaque byte strings chosen independently by each endpoint:
QUIC Long Header Packet (Initial/Handshake):
┌────────────────────────────────────────────────┐
│ Header Form=1 │ Fixed Bit=1 │ Packet Type (2b) │
│ Version (32 bits) │
│ DCIL (4b) │ SCIL (4b) │
│ Destination Connection ID (0-20 bytes) │
│ Source Connection ID (0-20 bytes) │
│ [type-specific fields] │
│ Payload (encrypted) │
└────────────────────────────────────────────────┘
Seamless Wi-Fi to Cellular Handoff
When a mobile device switches networks:
- The client gets a new IP address (e.g., from Wi-Fi's 192.168.1.x to cellular's 10.x.x.x)
- The client sends a PATH_CHALLENGE frame on the new path
- The server responds with PATH_RESPONSE, verifying the new path works
- The client confirms migration with a
NEW_CONNECTION_IDframe - Traffic continues on the new path — the HTTP/3 request in progress resumes without interruption
Time: [Wi-Fi]──────────────────────────[Cellular]
TCP: connection broken ──────▶[new TCP handshake, new TLS]
QUIC: ─────── seamless migration ──────────────▶
For a video call or file download, QUIC migration means the transfer continues without a visible interruption — no re-buffering, no dropped call.
Active Connection ID Management
To prevent linkability attacks (where an observer correlates connection IDs across path changes to track a user), QUIC recommends using a *new* Connection ID after each migration. Endpoints pre-provision multiple Connection IDs with NEW_CONNECTION_ID frames so the client can switch IDs atomically with path migration.