Retransmission as range inversion
QUIC never retransmits packets — it retransmits data. Here is how QUICO turns "what is missing?" into a single range operation.
TCP retransmits segments. QUIC does something subtler: a lost packet is never sent again. Its packet number is burned, and whatever data it carried is queued for a new packet. That one rule changes how you think about loss recovery.
Think in byte ranges, not packets
For every stream QUICO keeps two sets of byte ranges: what the peer has acknowledged, and what is in flight. Everything else between zero and the end of the stream is, by definition, data that still has to be sent.
// The entire retransmit logic:
const missing = flat_ranges.invert(
acked_ranges ∪ in_flight_ranges,
0, total_size
);When loss detection declares packet pn lost, we simply delete in_flight[pn]. The byte ranges it covered drop out of the in-flight set, the inversion picks them up as missing, and the next packet the sender builds carries them. No retransmission queue, no per-packet copies of payloads.
Why a flat representation
Ranges are stored as a sorted flat array [start0, end0, start1, end1, …]. Union and inversion are a single linear merge, and the common case — one contiguous acknowledged prefix plus a short tail — stays tiny. Allocation per ACK is close to zero, which matters in JavaScript where garbage is the hidden cost of every abstraction.
What it buys us
- Spurious loss is harmless: if the "lost" packet is acknowledged later, its ranges simply become acked.
- Re-packing is free: missing ranges from several lost packets coalesce into one full-size packet.
- The code is small enough to read in one sitting — which is the whole point of a pure-JS stack.
Loss detection itself (PTO timers, RFC 9002) is still on the roadmap; this post describes the data model it plugs into.