Short answer: Flow control protects the receiver using its advertised window (rwnd). Congestion control protects the network path using the sender's congestion window (cwnd) and algorithm. The smaller window normally limits outstanding data.
Before this: TCP header fields and the TCP handshake.
Two different problems, two different mechanisms
Both mechanisms affect "how much can I send right now," but they protect different resources.
Picture carts carrying parts to a workbench through a shared factory corridor. Flow control is the worker reporting how many more parts fit on the bench. Congestion control is the dispatcher limiting carts in transit when deliveries slow down or fail. The bench may be empty while the corridor is jammed, or full while the corridor is clear. With Explicit Congestion Notification (ECN), network devices can also mark congestion, like a warning light in the corridor; loss is not the only possible signal.
Flow control: The receive window
The Window Size field in the TCP header advertises a receive budget beginning at the acknowledgment number, with negotiated window scaling applied after the SYN exchange. It reflects how much data the receiving TCP is prepared to accept. If its application reads slowly, the receiver can advertise a smaller window, which the sender must respect. A window of 0 pauses normal new data in that direction, not ACKs or the entire connection. The sender periodically probes so it can discover when the receive window reopens.
Congestion control: What the sender infers on its own
The receive window says nothing about capacity along the path. The sender therefore maintains a congestion window (cwnd), which is not a TCP header field. The classic Reno-style model below responds to ACKs, inferred loss, and optionally ECN. Other algorithms, including CUBIC, use different growth and reduction rules. In the factory, cwnd is the dispatcher's cap on outstanding carts, not the workbench's free space.
- Slow start: cwnd starts at a small number of segments (historically as low as 1, larger in modern stacks) and roughly doubles every round-trip, growing exponentially, until it hits a threshold called ssthresh.
- Congestion avoidance: past ssthresh, growth switches to roughly linear, one segment's worth of extra room per round-trip, a much more cautious climb.
- Loss response: three duplicate ACKs trigger classic fast retransmit and recovery. Reno reduces its sending window roughly by half. Duplicate ACKs can also come from reordering, so this is an inference, not proof of a single dropped segment or mild congestion.
- Timeout response: when the retransmission timer expires without acknowledgment of the outstanding data, classic TCP reduces cwnd sharply and returns to slow start. This does not require total silence on the connection.
Worked example: the window is a byte budget
Suppose rwnd = 65,536 bytes, cwnd = 32,768 bytes, and 12,288 bytes remain unacknowledged. Ignoring recovery and other limits, the sender can put another min(65,536, 32,768) - 12,288 = 20,480 bytes in flight. The dispatcher has room for more carts, but only within the smaller of the corridor budget and the bench budget.
At a steady 50 ms round-trip time, a 32,768-byte flight limit corresponds to roughly 32,768 / 0.05 = 655,360 bytes/s, about 5.24 Mbit/s before overhead. This is a window-limited estimate, not a throughput promise. Loss, ACK behavior, pacing, sender data availability, and receiver processing can reduce it. A faster Ethernet link alone does not enlarge this budget.
Tell the limits apart
| Evidence | Check | Interpretation |
|---|---|---|
| Receiver advertises a zero window | Inspect receiver buffer pressure and whether the application reads promptly | Flow control is stopping new normal data; zero-window probes can still occur. |
| Retransmissions with an open receive window | Compare captures at both ends, ACK timing, and congestion signals | Loss or reordering may limit progress. A one-sided capture can also miss packets. |
| Small raw Window field in a capture | Inspect Window Scale options in both SYNs | Post-handshake windows may be scaled; the raw 16-bit field alone is not the effective byte limit. |
| Need the actual cwnd | Use sender-side TCP telemetry; Linux iproute2 offers ss -ti | cwnd is local sender state and cannot be read directly from a packet header. Tool units must be checked before comparing values. |
A sender never gets to use more than min(receive window, congestion window) worth of unacknowledged data at once. A fast local network with a slow, congested path to the destination will be limited by cwnd; a fast path to a receiver with a tiny buffer will be limited by the receive window. Either one alone tells an incomplete story.
Related topics and sources
Continue with MTU and fragmentation or HTTP transport behavior.