HTTP/3 carries HTTP over QUIC instead of TCP. QUIC offers independently ordered streams, so a missing range in one stream need not prevent delivery of already received data on another. This removes a particular transport ordering dependency. It does not give each request a separate congestion controller, loss detector, or guaranteed completion time.
Delivery ordering is not loss detection
HTTP/2 frames share TCP’s single ordered byte stream. A missing range blocks delivery of later bytes to HTTP, even when those bytes belong to another response. Previously delivered data remains available; a loss does not retroactively stop every application action. The affected requests depend on where their bytes fall relative to the gap.
QUIC maintains offsets within each stream. Received data on stream B can become available while stream A awaits a missing prefix. However, a QUIC packet can contain frames from several streams, so losing one packet can affect several streams together. Necessary information is sent again in new packets; packet identity and stream offsets serve different purposes.
RFC 9002 separates loss detection by packet-number space, not by HTTP request or stream. RTT estimation and congestion control describe the network path. Stream-level flow control coexists with connection-level credit. A shared capacity or credit shortage can therefore delay multiple streams despite independent ordering.
Keep HTTP semantics and deployment boundaries
Methods, status codes, and representation semantics remain HTTP semantics. HTTP/3 changes their framing and uses QPACK for field compression. QUIC integrates TLS 1.3 authentication and packet protection. Scheduling and priority policies still decide which available stream data to send. A field section that depends on unavailable QPACK dynamic-table entries can still block. Removing TCP ordering does not remove every protocol or application dependency, including shared database locks and server queues.
Lossy paths can benefit, but measure the actual workload rather than promising a universal speedup. CPU costs, handshake success, scheduling, bandwidth, and client implementation all affect results. Compare negotiated protocols and response completion on the same measured hop; an edge using HTTP/3 says nothing about its upstream protocol.
Clients can discover HTTP/3 through mechanisms such as Alt-Svc. When UDP connectivity prevents QUIC establishment, RFC 9114 recommends trying TCP-based HTTP. That is not a guarantee of immediate HTTP/2 fallback: client behavior and available server protocols matter. Operators still need reachable alternatives, appropriate advertisements, and measurements of failed attempts and fallback delay.
When it breaks
A firewall or middlebox can block QUIC traffic or interfere with usable datagram sizes. A successful advertisement is not evidence that a client established QUIC. Keep failures visible by network and protocol, and test fallback deliberately before relying on it for availability.
TLS resumption can permit 0-RTT application data before handshake completion. It is not simply replaying a ClientHello as an optimization, and accepted early requests can be replayed. Under RFC 8470, clients without additional information must not send unsafe methods in early data; idempotency alone is insufficient. A 425 response requires retrying outside early data, rather than treating an uncertain purchase as a fresh action.
Connection IDs can support migration after an address change, but successful continuation is conditional on protocol rules, configuration, routing, and path validation. NAT rebinding and deliberate migration are related but distinct cases. A load balancer that loses connection affinity can still break traffic. Migration is not permission to trust an arbitrary address or bypass validation.
Check it quickly
Save this as quic_streams.py and run python3 quic_streams.py with Python 3. It opens no sockets. The model compares delivery through one ordered transport with delivery ordered separately within two streams. Abstract chunks carry explicitly positioned bytes; there is no claim that these chunks reproduce packet framing or an actual recovery algorithm.
# Each tuple is a stream name, a one-byte offset, and that byte.
# Chunks are abstract delivery units, not QUIC packets or TCP segments.
def available(chunks, received, separate_streams):
buffered = {"A": {}, "B": {}}
for number, chunk in enumerate(chunks):
if number not in received:
if not separate_streams:
break # One ordered transport cannot expose later chunks.
continue
for stream, offset, byte in chunk:
assert len(byte) == 1
buffered[stream][offset] = byte
result = {}
for stream, offsets in buffered.items():
body = b""
while len(body) in offsets:
body += offsets[len(body)]
result[stream] = body
return result
chunks = [[("A", 0, b"a")], [("A", 1, b"b")],
[("B", 0, b"x")], [("B", 1, b"y")]]
received = {0, 2, 3} # The unit carrying A's second byte is missing.
independent = available(chunks, received, True)
ordered = available(chunks, received, False)
assert independent == {"A": b"a", "B": b"xy"}
assert ordered == {"A": b"a", "B": b""}
print("one missing A byte:", independent, ordered)
complete = {"A": b"ab", "B": b"xy"}
for separate in (False, True):
assert available(chunks, set(range(4)), separate) == complete
print("after repair: both models expose", complete)
# A single missing delivery unit can contain bytes for both streams.
shared = [[("A", 0, b"a")], [("A", 1, b"b"), ("B", 0, b"x")],
[("B", 1, b"y")]]
assert available(shared, {0, 2}, True) == {"A": b"a", "B": b""}
assert available(shared, {0, 1, 2}, True) == complete
# Even an independently ordered stream cannot skip its own missing prefix.
assert available(chunks, {0, 1, 3}, True) == {"A": b"ab", "B": b""}
print("negative controls: shared loss affects both; stream-local gaps still block")
print("delivery-order model only; no loss detector, timer, congestion, or wire test")
With the second A byte missing, independent ordering exposes A’s first byte and all of B, while the shared ordered transport exposes only A’s first byte. Supplying the missing chunk makes both results complete. No elapsed-time numbers are invented.
The negative controls matter: losing a chunk containing A and B data blocks both streams’ missing prefixes, and receiving B’s second byte alone cannot skip its first. This demonstrates only the stated ordering assumptions. It does not measure retransmission timing, congestion, flow-control exhaustion, QPACK, TLS, migration, or browser performance. Those require an actual implementation and separately authorized network experiments.
Further reading
Sources
- RFC 9000: QUIC, sections 2, 4, 8–9, 12–13: ordering, flow control, migration, packets, and retransmitted information, checked 2026-10-15.
- RFC 9002: QUIC recovery, section 6: packet-number-space loss detection and path-wide congestion behavior, checked 2026-10-15.
- RFC 9114: HTTP/3, HTTP mapping, discovery, and TCP fallback guidance, checked 2026-10-15.
- RFC 9204: QPACK, section 2.1.2: blocked field sections, checked 2026-10-15.
- RFC 9001: TLS with QUIC, section 9.2: 0-RTT replay considerations, checked 2026-10-15.
- RFC 8470: HTTP early data, sections 4–5: method safety and 425 retry behavior, checked 2026-10-15.
- Cloudflare: HTTP/3 and QUIC, introductory protocol background, not proof of this model, checked 2026-10-15.