Sign in
TodayMapLearnPracticeReview
Library
18 MINcoreNetworking & CommunicationNot started

TCP — Transmission Control Protocol

TCP is the connection-oriented, reliable, ordered transport protocol that carries most of the internet's traffic — HTTP, HTTPS, gRPC, SSH, SMTP, databases, message queues. It achieves reliability on top of an unreliable IP network through sequence numbers, acknowledgements, retransmission, flow control, and congestion control. Understanding TCP is understanding why your HTTP request takes 100ms before it can even send a byte.

Why this matters

TCP is the substrate of nearly every web service. Its handshake costs a round-trip before data flows. Its head-of-line blocking means one lost packet stalls every concurrent request on the connection. Its congestion control (slow start, AIMD) explains why a fresh connection is slow and why throughput ramps up over time. If you cannot reason about TCP, you cannot reason about latency, throughput, or the design decisions behind HTTP/2, HTTP/3, and QUIC.

Prerequisites
  • HTTP — HyperText Transfer Protocol
Related
  • UDP — User Datagram Protocol
  • TLS — Transport Layer Security
  • QUIC — The UDP Transport Powering HTTP/3
  • WebSockets — Full-Duplex Real-Time Over TCP
Used in
  • TLS — Transport Layer Security
  • UDP — User Datagram Protocol
Lesson

How it works

TCP sits on top of IP and adds four things IP does not give you: reliability (every byte arrives or you find out), ordering (bytes arrive in the order you sent them), flow control (the sender does not overwhelm the receiver), and congestion control (the network does not collapse under load). The cost of these guarantees is complexity, latency, and head-of-line blocking.

TCP is connection-oriented: before any data flows, the two endpoints run a handshake to agree on initial sequence numbers and capabilities. The connection has state — sequence numbers, window sizes, congestion variables — that both sides maintain for the lifetime of the connection. Closing a connection is itself a multi-packet ceremony (the four-way FIN/ACK handshake). This state is why TCP is heavier than UDP and why each connection consumes kernel memory (typically tens of KB).

TCP's reliability is built from three mechanisms layered on top of IP's best-effort delivery.

Sequence numbers and acknowledgements. Every byte sent has a sequence number. The receiver acknowledges the next byte it expects. If the sender does not see an ACK within the retransmission timeout (RTO, typically 200ms-1s, computed from smoothed RTT measurements), it resends. This is how TCP recovers from packet loss without involving the application.

Cumulative ACKs. An ACK for byte N means 'I have received everything up to N-1.' If the receiver gets bytes 1, 2, 4 (3 was lost), it ACKs byte 3 — telling the sender 'I am still waiting for 3.' The sender's retransmission timer eventually fires for byte 3.

Fast retransmit. When the sender sees three duplicate ACKs for the same byte, it infers a loss and retransmits immediately without waiting for the RTO. Three dup-ACKs is a strong signal because each dup-ACK means a later segment arrived — the missing one is probably gone, not just delayed.

These mechanisms give you the guarantee every web developer takes for granted: you send bytes, the receiver gets them in order, or you find out the connection died. The application never has to deal with lost or reordered packets.

Flow control protects the receiver. The receiver advertises a 'window' in every ACK — how many bytes of buffer it has free. The sender never sends more than the window. If the receiver's app is slow to read, the window shrinks; the sender slows down. If the window hits zero, the sender periodically probes with one byte to detect when the window reopens. Flow control is purely point-to-point: it stops the sender from overwhelming the receiver.

Congestion control protects the network. Without it, every TCP sender would blast packets as fast as their sender window allowed, and routers would drop packets en masse, triggering retransmissions, triggering more drops — congestion collapse. The internet survived the 1986 congestion collapse because Van Jacobson added congestion control to TCP in 1988.

The classic algorithm has four phases:

  • Slow start: start by sending a small window (1-10 segments), double it every RTT without loss. Exponential growth gets you to bandwidth quickly.
  • Congestion avoidance: when the window crosses a 'slow start threshold', switch to linear growth — add one segment per RTT. Probe for bandwidth gently.
  • Fast retransmit: on three dup-ACKs, retransmit the missing segment without waiting for RTO.
  • Multiplicative decrease: on loss, halve the window (and reset slow-start threshold to half the current window). AIMD — additive increase, multiplicative decrease — is provably fair across competing flows.

Modern variants (CUBIC, BBR) tune the math, but the structure is the same: probe up, back off hard on loss. The practical consequence: a fresh TCP connection starts slow and ramps up over ~10 RTTs to its steady-state throughput. This is why persistent connections and connection pools are critical for performance — you do not want to pay slow start on every request.

Head-of-line blocking — TCP's biggest modern problem

TCP guarantees in-order delivery. If segment 3 of a stream is lost, segment 4 (which arrived fine) sits in the receiver's buffer until segment 3 is retransmitted. The application cannot see segment 4 even though it is there. This is head-of-line blocking. On a single HTTP/1.1 connection it is manageable (you are processing one request at a time). On an HTTP/2 multiplexed connection, it is brutal: 50 parallel requests all stall because one packet was lost, even though only one logical stream cares about that packet. This is the reason HTTP/3 abandons TCP for QUIC.

TCP vs UDP is the transport decision. TCP offers reliability, ordering, flow control, and congestion control — at the cost of setup latency (1 RTT handshake), state per connection, head-of-line blocking, and the kernel doing work on every packet. UDP offers none of those — you send datagrams, they may arrive, may not, may arrive out of order. The application gets a thin pipe and builds what it needs on top.

Use TCP when:

  • The data must arrive completely and in order (HTTP, file transfer, database queries, SSH).
  • You do not want to reimplement reliability.
  • The latency cost of the handshake is amortized over a long connection (keep-alive, connection pools).

Use UDP when:

  • Late data is useless (voice, video — a 200ms-old audio frame is noise, retransmitting it makes things worse).
  • The application has its own reliability semantics (DNS — just retry the whole query).
  • You need to avoid head-of-line blocking across independent streams (QUIC, HTTP/3).
  • You are building a custom congestion control (WebRTC, QUIC).

The modern trend is to push reliability, ordering, and congestion control out of the kernel and into the application layer over UDP — so the application can make smarter decisions (per-stream reliability, 0-RTT setup, connection migration). QUIC is the proof: it implements TCP's guarantees on top of UDP, adds TLS, removes head-of-line blocking, and now carries a meaningful fraction of the internet's traffic.

Check yourself
solid

Why does a fresh TCP connection feel slow for the first few requests, even on a fast network?

Pick one answer.

Check yourself
interview

On an HTTP/2 connection multiplexing 50 concurrent requests, one packet is lost. What is the impact, and why does it motivate HTTP/3's move to QUIC over UDP?

Pick one answer.

Check yourself
core

Which of these applications should choose UDP instead of TCP, and why?

Pick one answer.

The three-way handshake, annotated. Why does TCP need three packets, not two? The goal is for both sides to agree on initial sequence numbers (ISNs). Sequence numbers are not '1, 2, 3...' — they start at a random 32-bit value to prevent packet injection attacks (an attacker who can predict the ISN can forge packets that look like they belong to an existing connection).

The flow:

  1. SYN — Client picks a random ISN x and sends SYN, seq=x. This says 'I want to talk; my stream will start at byte x.'
  2. SYN+ACK — Server picks its own random ISN y and sends SYN, seq=y, ack=x+1. This says 'I want to talk too; my stream starts at byte y; I acknowledge your SYN by sending ack=x+1 (meaning I expect byte x+1 next, which proves I got your x).'
  3. ACK — Client sends ACK, ack=y+1, proving it received the server's SYN.

After step 3, both sides know each other's ISNs and can begin exchanging data. The first byte of application data can be piggybacked on this ACK (TCP Fast Open), but most clients don't use it.

This is 1 RTT — one round trip from SYN to data-flow. For HTTPS, add 1 RTT for TLS 1.3 (or 2 RTT for TLS 1.2). So a fresh HTTPS connection to a server 100ms away costs 200-300ms before the first byte of HTTP response. This is why connection reuse is so valuable: amortize the handshake across many requests.

How the Internet Works — DNS, TCP, HTTP explained— Supplementary explanation. The NO CAP lesson remains self-contained.

Flow control vs congestion control — same shape, different problems. Both use a 'window' (a cap on in-flight bytes), but they protect different things and respond to different signals.

AspectFlow controlCongestion control
ProtectsThe receiver (don't overflow its buffer)The network (don't collapse it)
SignalReceiver advertises rwnd in every ACKInferred from loss (3 dup-ACKs or RTO timeout)
Window nameReceive window (rwnd)Congestion window (cwnd)
Effective windowmin(rwnd, cwnd)min(rwnd, cwnd)
AlgorithmSliding window, fixed cap per ACKSlow start → congestion avoidance → fast retransmit → multiplicative decrease
Tunable by app?No — kernel-managedNo — kernel-managed (BBR/CUBIC selectable)

The sender's actual send window is min(rwnd, cwnd) — the lesser of what the receiver can absorb and what the network can carry. If the receiver is slow (small rwnd), flow control binds. If the network is lossy (small cwnd), congestion control binds. They're independent mechanisms that compose.

The practical implication for system design: if a TCP connection feels slow to ramp up, it's almost always congestion control (slow start). If it stalls mid-stream, it's almost always flow control (the receiver app isn't reading fast enough). Diagnose accordingly.

Real system: how HTTP/3 solves TCP's limitations

HTTP/3 runs over QUIC, which is itself over UDP. QUIC reimplements TCP's reliability (sequence numbers, ACKs, retransmission, congestion control) in user-space — but with three key differences TCP cannot match: (1) Independent streams — a loss on stream 3 doesn't block streams 1, 2, 4-20. (2) 0-RTT connection setup — resumed connections send HTTP data in the very first packet by combining the TLS handshake with the QUIC handshake. (3) Connection migration — a QUIC connection is identified by a connection ID, not a 4-tuple, so when your phone switches from Wi-Fi to cellular (IP changes), the connection survives — no re-handshake. Google measured a 3% improvement in YouTube watch time after deploying QUIC. Cloudflare serves ~50% of its traffic over HTTP/3 today. The cost: QUIC is implemented in user-space, so it consumes more CPU than kernel TCP — but the latency wins are worth it for most workloads.

Check yourself
interview

Your mobile app keeps a long-lived HTTPS connection to your backend. Users complain that when they switch from Wi-Fi to cellular mid-session, the app freezes for 5+ seconds before recovering. What's happening, and what fixes it?

Pick one answer.

Try this
interview

Each new TCP connection to Postgres pays: TCP handshake (1 RTT), TLS handshake if used (1-2 RTT), Postgres startup message + auth (1 RTT), and TCP slow start on the first query (several RTTs to ramp up cwnd). On a 1ms-RTT LAN this is invisible. On a 5ms-RTT link, each new connection is ~30ms of setup before any data flows.

Your Postgres-backed API opens a new database connection per request to keep the code simple. At 100 RPS this works fine. At 1,000 RPS, latency jumps from 20ms to 400ms and Postgres starts refusing connections. Diagnose the root cause and pick the fix.

Engineering mental model

Mental model. Think of TCP — Transmission Control Protocol as a deliberate boundary in a system. The boundary exists because something becomes harder to manage when everything is done in one place: latency, scale, failure isolation, consistency, cost, or team ownership. The useful question is not “what does TCP — Transmission Control Protocol mean?” but “what pressure makes this boundary worth introducing, and what new failure mode does it create?”

Design lens

Before choosing TCP — Transmission Control Protocol, name the workload, the critical user path, the dominant bottleneck, the failure you are trying to absorb, and the trade-off you are willing to accept. If you cannot name those five things, the design is probably premature.

Original NO CAP systems visual for TCP — Transmission Control Protocol.
Image unavailable. Original NO CAP systems visual for TCP — Transmission Control Protocol.
TCP — Transmission Control Protocol: a compact system-thinking visual.— Original NO CAP visual.
// Pseudocode
request = receive()
result = tcp(request)
return result

// Production questions:
// 1. What happens on timeout?
// 2. Can this operation be retried safely?
// 3. What is the bottleneck?
A minimal engineering sketch for reasoning about TCP — Transmission Control Protocol.

Back-of-the-envelope reasoning

Numerical lens: write down traffic, payload size, read/write ratio, peak multiplier and durability target before choosing a component. The numbers should justify the architecture.

Interactive sandboxdeterministic

Interactive thought experiment: TCP — Transmission Control Protocol

Change the variables below and predict what breaks first in TCP — Transmission Control Protocol. The production lab can later reuse these same inputs.

System pressure6%
Try this

Change one variable at a time. Predict the failure mode first, then move the slider and see whether your mental model matches the simplified system response.

Hint

If you are stuck on TCP — Transmission Control Protocol, start by drawing the request path and marking every network hop, stateful component, queue, cache and failure boundary. Then estimate where the system will saturate.

Check yourself
solid

You increase traffic by 10× in a system using TCP — Transmission Control Protocol. What should you inspect first?

Pick one answer.

Check yourself
interview

Which statement is the safest engineering habit when using TCP — Transmission Control Protocol?

Pick one answer.

Try this
interview

You have dashboards for traffic, latency, errors and saturation. You can change the architecture, but every change has operational cost.

Production scenario: your system uses TCP — Transmission Control Protocol, traffic suddenly spikes, and p99 latency doubles. What is your first move?

Interview drill

Answer this without notes: When would you choose TCP — Transmission Control Protocol, and when would you intentionally avoid it? Mention at least one bottleneck it addresses, one failure mode it introduces, and one alternative. Then quantify the workload you are designing for.

Engineering lens

A useful engineering lens for TCP - Transmission Control Protocol: define the problem it solves, the simpler design that fails first, the constraint that forces you to introduce this concept, and the new failure modes the concept creates.

Numerical sanity check

Back-of-the-envelope reasoning beats fake precision. State your traffic, payload, concurrency and growth assumptions explicitly, then calculate enough to know whether the current architecture is orders of magnitude away from the target.

Check yourself
interview

Do not optimize for a memorized definition. Reason from the workload and failure mode.

Imagine the simplest version of a system using TCP - Transmission Control Protocol. What breaks first as traffic grows by 10×, and what would you change before reaching 100×?

Pick one answer.

Trade-offs

What you gain, what you pay

Pros
  • +Reliability built in — the application does not deal with lost or reordered packets.
  • +In-order byte stream — trivial to reason about at the application layer.
  • +Mature congestion control keeps the network stable under load.
  • +Battle-tested for decades; every OS kernel implements it well.
Cons
  • −Handshake costs 1 RTT before any data — a tax on short-lived connections.
  • −Head-of-line blocking — one lost packet stalls everything on the connection.
  • −Connection state in the kernel — limits the number of concurrent connections a server can hold.
  • −Slow start penalizes fresh connections; you want keep-alive or pooling.
  • −TCP is in the kernel — iterating on it (e.g., new congestion control) takes years.
Failure modes

How this breaks in production

  • Slow start on every new connection makes short connections slow — mitigated by keep-alive and connection pools.
  • Head-of-line blocking on multiplexed connections (HTTP/2) — motivating the move to QUIC.
  • TIME_WAIT exhaustion on servers under high connection churn — mitigated by SO_REUSEADDR / SO_REUSEPORT.
  • Bufferbloat — oversized buffers defeat congestion control and inflate latency.
  • NAT timeouts killing idle connections — mitigated by application-level keepalives.
Common mistakes

Don't fall into these traps

  • •Opening a new TCP connection per request instead of using keep-alive — pays the handshake and slow-start cost every time.
  • •Treating TCP as 'infinite throughput' — every connection pays slow start and AIMD.
  • •Assuming TLS over TCP is cheap — TLS 1.2 adds 2 RTT, TLS 1.3 adds 1 RTT on top of the TCP handshake.
  • •Forgetting that TCP guarantees byte ordering, not message boundaries — you must frame your messages yourself.
  • •Ignoring TIME_WAIT — a server under connection churn can exhaust ephemeral ports.
  • •Blaming the network for what is actually slow start on a fresh connection.
Where you see it

Real systems using this

Every HTTP/1.1 and HTTP/2 connection on the web.Database driver connections (PostgreSQL, MySQL, MongoDB).SSH, SFTP, SMTP — classic TCP applications.gRPC (runs over HTTP/2, which runs over TCP).WebSockets (HTTP upgrade over TCP).
Teardowns

How real systems implement this

  • HTTP/2 — Multiplexes multiple HTTP streams over a single TCP connection. Solves application-layer head-of-line blocking but inherits transport-layer head-of-line blocking from TCP.
  • PostgreSQL — Each client connection is a long-lived TCP socket; libraries use connection pools to amortize the TCP handshake and slow start across many queries.
  • TCP BBR (Google) — Modern congestion control that models bandwidth and RTT instead of reacting to loss. Deployed on Google's backbone and YouTube; reduces latency and improves throughput on lossy links.
Interview prompts

Practice saying it out loud

  • Q1Walk through the TCP three-way handshake. Why does it cost one RTT before any data can flow?
  • Q2What is head-of-line blocking, and why does HTTP/3 move to QUIC over UDP to fix it?
  • Q3Explain slow start and congestion avoidance. Why does this matter for connection pooling?
  • Q4TCP vs UDP — give three real systems and explain which transport each should use and why.
  • Q5What happens at the TCP layer when you type https://example.com and hit Enter?
Research

Further reading & references

System Design Primer
Open source
ByteByteGo — Scale from zero to millions
ByteByteGo
System Design Tutorial
GeeksforGeeks
System Design Roadmap
roadmap.sh
Networking & Communication reference
Reference
Networking & Communication reference
Reference
Networking & Communication reference
Reference
Cloudflare Learning Center
Cloudflare

Core explanations are original NO CAP material. External references are provided for deeper study and standards.

What next?

Mark as understood once the mental model clicks.

Next recommended

UDP — User Datagram Protocol