JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

How TCP Makes an Unreliable Network Reliable

The network underneath loses packets, scrambles their order, and corrupts bits — yet TCP hands your program a perfect, ordered byte stream. This guide opens the box and shows you the small set of tricks that pull off that magic.

The promise on top of a broken pipe

In the last guide we put UDP and TCP side by side: UDP is a postcard, TCP is a phone call with a transcript. Now we crack TCP open and ask the deep question — how does it keep that transcript perfect? Remember that the network layer below it offers only best-effort delivery. A packet might be dropped when a queue overflows, arrive out of order because two packets took different routes, get duplicated, or have a bit flipped by electrical noise. The network promises nothing. TCP, somehow, promises everything.

The honest answer is that TCP does not fix the network. It cannot stop a packet from being lost. What it does is build reliable data transfer on top of an unreliable channel using only checks and retries at the two ends — exactly the end-to-end principle in action. The trick is a small toolkit that, working together, lets the receiver detect every kind of damage and the sender repair it. Four tools do the heavy lifting: a checksum, sequence numbers, acknowledgments, and a timer. A fifth, the sliding window, makes the whole thing fast instead of painfully slow.

Catching corruption: the checksum

Before TCP can recover from loss, it has to notice damage. That is the checksum's job. When the sender builds a segment, it adds up the bytes in a defined way and writes the result into the header. The receiver redoes the same sum on what it received and compares. If the two don't match, a bit got flipped in transit, and the whole segment is thrown away — treated exactly as if it had never arrived. From there, the loss-recovery machinery takes over.

Be honest about what a checksum can and cannot do. It can detect common errors cheaply, but TCP's checksum is a relatively weak one — it can miss some patterns of corruption, which is one reason careful applications add their own stronger integrity checks. And detecting is not fixing: TCP does not try to repair the bad bits in place. It simply discards the segment and relies on retransmission to send a fresh copy. Detection plus resend, not repair, is the whole strategy.

Numbering, acking, and the timer: the recovery loop

Now for ordering and loss. TCP stamps every byte of the stream with a sequence number — think of it as the page number on a long letter split across many envelopes. With page numbers, a receiver can lay bytes down in the right order even when segments arrive scrambled, and it can spot a duplicate (a page it already has) or a gap (a page that's missing). The numbers count bytes, not packets, and don't start at 1: each side picks a fresh, hard-to-guess initial sequence number during the handshake.

The receiver reports progress with an acknowledgment (an ACK). TCP's ACKs are cumulative: an ACK number says "I have every byte up to here; the next one I want is N." So an ACK of 5001 means "I hold bytes 1 through 5000; send 5001 next." The sender watches the ACKs roll in. If an expected ACK never comes back, a retransmission timer eventually fires and the sender resends the unacknowledged data. The timeout is tuned from measured round-trip times — long enough not to resend merely-slow data, short enough not to sit idle after a real loss.

  1. Sender ships segments carrying bytes 1–1000, 1001–2000, 2001–3000, 3001–4000.
  2. The 1001–2000 segment is lost in a congested queue. The other three arrive fine.
  3. The receiver, missing the gap, keeps replying ACK 1001 — "I still only have up to 1000; I'm waiting for 1001." It buffers 2001–4000 but cannot deliver them in order yet.
  4. The sender's timer for the missing bytes expires (or three duplicate ACK 1001s arrive — see below), so it resends bytes 1001–2000.
  5. The receiver finally has 1001–2000, snaps it into the gap, and now delivers 1001 through 4000 to the application in one clean run, replying ACK 4001.

Why one-at-a-time is too slow: the sliding window

There is a naive way to be reliable: send one segment, wait for its ACK, then send the next. This is stop-and-wait, and it is correct but agonizingly slow. Picture a link with a 100 ms round-trip time. Sending one small segment and waiting a full 100 ms before sending the next means the link sits idle almost the entire time — you'd use a tiny fraction of a fast pipe's capacity. The problem isn't bandwidth; it's that you're waiting on geography between every single packet.

The fix is the sliding window: let many segments be "in flight" — sent but not yet acknowledged — at once, instead of one. The window is the allowed amount of unacknowledged data. As ACKs come back and confirm the earliest bytes, the window slides forward and the sender is permitted to release new bytes at the front, keeping the pipe continuously full. This is pipelining, and it's how TCP turns a correct-but-crawling scheme into something that can saturate a fast, long link. How big should the window be? Ideally about the bandwidth-delay product — enough bytes to keep the whole pipe full for one round trip.

stop-and-wait (one round trip per segment):
  send 1 ---100ms---> ack ---100ms---> send 2 ---100ms---> ack ...
  link mostly IDLE; throughput tiny on a long link

sliding window (many in flight at once):
  send 1,2,3,4,5,6,7,8 ----> ----> ----> acks streaming back
  |<-------- window -------->|
  as ack for 1 arrives, window slides: now 9 may be sent
  link stays FULL; throughput limited by capacity, not RTT
A window of in-flight data keeps the pipe full instead of idling between ACKs.

Catching loss faster, and the price of order

Waiting for a timer to expire is the slow path to detecting loss. TCP has a faster sense. Recall that when the receiver gets out-of-order data, it keeps re-sending the same cumulative ACK ("still waiting for 1001"). If the sender sees three duplicate ACKs for the same byte, that is a strong hint the next segment was lost while later ones got through — so it resends immediately without waiting for the timeout. This is fast retransmit, and it is why TCP usually recovers from a single drop in roughly one round trip rather than one long timeout.

All of this is genuinely clever, but be honest about the cost. The in-order guarantee means the receiver must hold back any bytes that arrived after a gap until the missing bytes are filled in — those later, correctly-received bytes sit useless in a buffer, waiting. That stall is called head-of-line blocking, and it is the unavoidable flip side of "in order, no matter what." It's exactly why a protocol like QUIC, which runs over UDP and can let independent streams advance separately, was created to sidestep TCP's single-stream stall — a thread we'll pull on in the next two guides.