Flow Control & Sliding Window

intermediate tcp flow-control sliding-window rwnd transport

Flow control is how TCP makes sure a fast sender doesn’t drown a slow receiver. The mechanism is the sliding window — the receiver tells the sender how much room it has, and the sender sends no more than that.

In simple language: the receiver says “I can handle 32 KB right now.” The sender sends up to 32 KB without waiting, then pauses for an ACK that updates the window.

The Problem

Without flow control, a sender on a fast link could fire off megabytes per second. A receiver running on an old phone might only be able to consume a few KB/sec. The phone’s buffer overflows, packets get dropped, everyone suffers.

Flow control = “send only what I can hold.”

The Receive Window (rwnd)

Every TCP segment carries a Window field (16 bits, optionally scaled). It says: “I can buffer this many more bytes from you right now.”

The sender tracks this and never sends more unacked data than the window allows.

sender sees: rwnd = 32768 bytes, unacked = 20000
            -> can send 12768 more bytes before stopping

The Sliding Window Visualized

Sender's Buffer (sliding window)
ACKED
SENT, NOT ACKED
CAN SEND NOW
CAN'T SEND YET
past
in flight
window
future
• ACKs from receiver slide the window right, freeing space.
• Sender can keep up to (sent - acked) ≤ rwnd bytes in flight.

How ACKs Slide the Window

Sender state: sndUna = 1000, sndNxt = 5000, rwnd = 8000.

That means: bytes 1000–4999 are in flight. We can send up to byte 9000 (1000 + 8000) before stopping.

When an ACK comes in for ack=3000, we slide:

Before: [acked < 1000][in flight 1000..4999][can send 5000..8999]
After:  [acked < 3000][in flight 3000..4999][can send 5000..10999]   <- window slid right

We unlocked 2000 more bytes worth of sending room.

Window Size 0 — “Stop”

If the receiver’s app is too slow, its buffer fills up. The receiver sends an ACK with window = 0. The sender must stop sending until a window update arrives.

But what if that window update is lost? The sender would deadlock forever.

Zero-Window Probes

The sender periodically sends a 1-byte probe to keep the conversation alive. The receiver replies with the current window. If the window is still 0, the probe is rejected. If it’s now 100, the sender resumes.

Window Scaling

The 16-bit Window field maxes out at 65,535 bytes. On modern fast links, that’s tiny. Solution: window scaling option (RFC 7323) — both sides agree on a shift factor at handshake time, multiplying the advertised window by 2^N.

With window scaling, windows can be hundreds of MB.

Bandwidth-Delay Product (BDP)

The “right” window size is the bandwidth-delay product:

BDP (bytes) = bandwidth (bytes/sec) × RTT (sec)

A 1 Gbps link with 100ms RTT needs ~12.5 MB of in-flight data to keep the pipe full. If the window is smaller, we’re wasting bandwidth.

Flow Control vs Congestion Control

Easy mix-up — they’re different things:

  • Flow control (rwnd) — protects the receiver from overload.
  • Congestion control (cwnd) — protects the network from overload.

The sender uses min(rwnd, cwnd) as the actual amount it can send. We’ll cover congestion control in the next note.

Inspecting It

# On Linux, see the current send/recv windows for a connection
ss -tin
# State  Recv-Q  Send-Q  ...  cwnd:10  rcv_space:65483

# tcpdump shows the window field
sudo tcpdump -nn -i any 'tcp port 443'
# ... Flags [.], ack 12345, win 502, ...   <- 502 (with scaling factor applied)

Common Gotcha

A 0-window stall looks like the connection is dead to a casual observer. ss shows ESTABLISHED, but no data is moving. Check Recv-Q on the slow side — if it’s full, the app isn’t reading fast enough.

Interview Tip

If asked “why does TCP have a window?” — flow control is the answer. If asked “why does it slide?” — to allow continuous sending without waiting for each ACK individually (that would be a stop-and-wait protocol, very slow).