See It Flow
Interactive guide · 6 chapters · ~14 min read

TCP Engineering Insights

The design decisions, trade-offs and comparisons that separate knowing TCP's mechanics from actually understanding why it was built this way.

Mangesh MandlikLast updated:
01 · Design rationale

Why TCP is Reliable

Reliability in TCP isn't one feature — it's the composition of several independent mechanisms, each covering a different failure mode.

Loss

Covered by sequence numbers + acknowledgements + retransmission (timeout or fast retransmit).

Duplication

Covered by sequence numbers — a receiver simply discards a segment whose bytes it has already seen.

Reordering

Covered by sequence numbers — the receiver buffers out-of-order segments and reassembles them before delivering to the application.

Corruption

Covered by the checksum field in the TCP header — a corrupted segment is dropped and treated as if lost.

02 · Trade-offs

Why UDP Still Exists

If TCP is more conservative about correctness, why hasn't UDP disappeared? Because many applications value timeliness, message boundaries, or custom recovery policy more than TCP's default retransmission behavior.

For real-time media (voice, video, gaming), a packet that arrives late is often worse than one that never arrives at all — by the time TCP retransmits a lost audio frame, the conversation has already moved on. UDP also fits tiny request/response exchanges like DNS, where the application can retry or fail over without paying a full connection setup first. In both cases, UDP leaves timing and recovery policy to the application instead of forcing a one-size-fits-all retransmission strategy.

03 · The most-confused pair

Flow Control vs Congestion Control

These two mechanisms both limit how much data can be in flight — but they protect completely different things.

Flow control (rwnd)

Receiver-driven. Protects the receiving application's buffer from being overwhelmed. Advertised explicitly in every ACK.

Congestion control (cwnd)

Sender-driven and self-imposed. Protects the shared network path from being overwhelmed. Inferred indirectly from ACKs and loss — never advertised by anyone.

04 · A famous optimization (and its downside)

Nagle's Algorithm

Nagle's algorithm batches small writes to avoid flooding the network with tiny, inefficient packets — a great idea for bulk transfer, a common trap for latency-sensitive apps.

Without Nagle, an application writing one byte at a time (for example, an old telnet client) would generate a full TCP/IP header for nearly every payload byte — extremely wasteful. Nagle therefore says: if small data is ready to send but earlier data is still unacknowledged, buffer the new small write until either an ACK arrives or enough data accumulates to fill a segment.

textIllustrative timeline — write-write-read client, 1 ms RTT, receiver ACK delay ~40 ms (constructed for teaching)
Nagle ON (default)
  t=0 ms    write(header, 20 B)   → sent at once (no unacked data)
  t=0 ms    write(body, 100 B)    → HELD: 20 B unacked and 100 B < MSS
  t≈40 ms   receiver's delayed-ACK timer fires → ACK for the 20 B
  t≈41 ms   body sent; server can finally reply          ⇒ ~41 ms per request

TCP_NODELAY set
  t=0 ms    header sent, body sent immediately
  t≈1 ms    server has both and replies                   ⇒ ~1–2 ms per request

The pattern that triggers it is write-write-read: two small writes for one logical message followed by a read. The cleaner fix is often to coalesce the message into a single write()/writev() so there is only one small segment, instead of relying on TCP_NODELAY alone. Delayed ACK timing depends on the operating system, TCP implementation and connection state. The approximately 40 ms delay shown here is an illustrative scenario, not a universal Linux guarantee; RFC 1122 caps delayed ACKs at 500 ms.

05 · Anti-patterns

Common Mistakes

A short list of TCP misunderstandings that show up repeatedly in both production incidents and interviews.

Treating TCP as message-oriented

TCP has no concept of message boundaries — write() and read() sizes are not guaranteed to match. Applications must implement their own framing (length prefixes, delimiters).

Ignoring the effective window

The usable window is always min(cwnd, rwnd) — tuning only one side while ignoring the other won't fix a throughput problem.

Assuming ACK = application processed

An ACK only confirms the TCP stack received the bytes — it says nothing about whether the receiving application has actually read or processed them yet.

06 · Where this shows up

Real-World Architectures

TCP's design shows up, directly or by reaction, all over modern infrastructure — from HTTP connection reuse to QUIC's deliberate choice to run over UDP.

HTTP/1.1 & HTTP/2 keep-alive

Both build directly on TCP connection reuse to amortize the handshake (and TLS handshake) cost across many requests.

HTTP/3 & QUIC

A direct reaction to TCP's head-of-line blocking — QUIC runs over UDP and implements its own reliability per-stream, so one lost packet doesn't stall unrelated streams.

Load balancers & connection pooling

Backend connection pools exist specifically to avoid paying TCP's handshake cost on every request to a database or internal service.

Key takeaways
  • TCP's reliability is a composition of four independent mechanisms: loss recovery, duplicate detection, reordering, and corruption detection.
  • UDP exists because retransmission isn't always the right answer — some applications need to control their own loss policy.
  • Flow control protects the receiver; congestion control protects the network — never conflate the two in an explanation.
  • Nagle's algorithm + delayed ACKs is a classic latency trap for small, frequent writes — TCP_NODELAY is the standard fix.
  • Modern protocol evolution (HTTP/2, HTTP/3/QUIC) is best understood as a direct response to specific TCP trade-offs.