Introduction

A TCP receiver places arriving bytes in a receive buffer, from which the application reads them when it chooses. If the sender sends faster than the application reads, the buffer fills and further bytes would be lost. Flow control prevents this: in every segment the receiver advertises a receive window (rwnd), modelled here as free receive-buffer space (implementation window-allocation policies can differ), and the sender uses the latest accepted window to limit new transmissions. A reduced rwnd or cwnd can temporarily be smaller than data already in flight; it does not withdraw already-sent bytes.

This is a sliding-window protocol (Sliding Window Protocols) with two differences: the window counts bytes, not frames, and its size changes during the connection as the receiver's buffer fills and empties. Flow control protects the receiver; congestion control (TCP Congestion Control) protects the network, and the sender obeys the smaller of the two windows.

The 16-bit window and 32-bit sequence number also have limits on fast links: the window scale option and sequence-number wraparound.

Prerequisites

Core concepts

The receiver's side

For this simplified buffer model, assume all received data is in order and no out-of-order bytes are buffered. In general, actual free buffer space is RcvBuffer minus all unread buffered bytes, including out-of-order data; this is not a complete implementation window-allocation policy. With buffer size RcvBuffer, the last byte the application has read LastByteRead, and the last byte received in order LastByteRcvd:

rwnd=RcvBuffer−(LastByteRcvd−LastByteRead).\text{rwnd} = \text{RcvBuffer} - (\text{LastByteRcvd} - \text{LastByteRead}).

The receiver puts rwnd in the window field of every segment it sends, together with the ACK number.

The sender's side

The sender keeps

NewSendAllowance=max⁡(0,min⁡(rwnd,cwnd)−FlightSize),\text{NewSendAllowance}=\max(0,\min(\text{rwnd},\text{cwnd})-\text{FlightSize}),

where FlightSize=LastByteSent−LastByteAcked\text{FlightSize}=\text{LastByteSent}-\text{LastByteAcked} in this simplified in-order model. Without congestion control, omit cwnd and use max⁡(0,rwnd−FlightSize)\max(0,\text{rwnd}-\text{FlightSize}). This is permission for new data, not an invariant on previously transmitted data after a window reduction. The window's left edge advances when ACKs arrive; its advertised right edge is ack+rwnd\text{ack}+\text{rwnd}.

Zero window and the persist timer

When rwnd = 0 the sender stops. The receiver will announce new space in a window update, but that segment carries no data and is not acknowledged; if it were lost both sides would wait forever. So a sender facing a zero window starts a persist timer and, when it fires, sends a window probe (one byte, or a segment the receiver must answer) to draw out the current window.

Silly window syndrome

If the application reads one byte at a time and the receiver advertises every freed byte, the sender sends one-byte segments, each with 40 bytes of headers. Two fixes:

  • Receiver side (Clark's solution): do not advertise a small window; advertise 0 until the free space is at least min⁡(MSS,RcvBuffer/2)\min(\text{MSS}, \text{RcvBuffer}/2).
  • Sender side (Nagle's algorithm): while any data is unacknowledged, hold back small segments; send them together when the ACK arrives or a full MSS has accumulated. Interactive applications that need each keystroke at once may switch it off.

Delayed ACKs. A receiver may also wait before acknowledging, so one ACK can cover two segments and carry a window update or reply data; RFC 1122 requires the delay to stay under 0.5 seconds and an ACK for at least every second full-sized segment.

Simplified in-order model with no out-of-order bytes buffered: a receive buffer of 4096 bytes drawn as a bar: a region of bytes already read by the application on the left (up to LastByteRead), then buffered bytes waiting to be read (up to LastByteRcvd), then free space labelled rwnd. Below it the sender's byte line: bytes acknowledged, bytes sent but not acknowledged, bytes allowed to be sent, and bytes not allowed; the advertised send window runs from the ACK number to ACK plus rwnd; the usable new-data allowance is max(0, rwnd minus outstanding). A small timeline shows the window shrinking to 0, a persist-timer probe, and an update to 1024 after the application reads 1024 bytes.
Figure 1. Receiver buffer and sender window (simplified in-order model, no out-of-order bytes buffered): rwnd = RcvBuffer minus buffered bytes; new data is limited to max(0, rwnd minus outstanding), and an advertised reduction can leave earlier-sent data beyond the new right edge, and a zero window is reopened by an update to 1024 after a probe.

Mathematical explanation: limits of the header fields

Throughput bound of the window

At most one window is delivered per round-trip time, so

throughput≤windowRTT.\text{throughput} \le \frac{\text{window}}{\text{RTT}} .

The 16-bit window field allows at most 65,535 bytes, so without scaling a connection with RTT 100 ms cannot exceed 65,535×8/0.1≈5.2465{,}535 \times 8 / 0.1 \approx 5.24 Mbps, however fast the link. To keep a link busy the window must be at least the bandwidth-delay product (Performance Metrics).

Window scaling

The window scale option (RFC 7323), sent on the SYN, gives a shift count SS between 0 and 14; the true window is the 16-bit field times 2S2^S. The largest window is about 216⋅214=2302^{16} \cdot 2^{14} = 2^{30} bytes (1 GiB). The limit 14 keeps the window below 2302^{30}, which leaves room in the 2322^{32} sequence space for old and new data to be told apart. To support window WW, the smallest shift is the least SS with 65,535⋅2S≥W65{,}535 \cdot 2^S \ge W.

Sequence-number wraparound

Sequence numbers are 32-bit byte counts, so they repeat after 2322^{32} bytes. At bandwidth BB bit/s the wrap time is

Twrap=232×8B.T_{\text{wrap}} = \frac{2^{32} \times 8}{B}.

If TwrapT_{\text{wrap}} is less than the maximum segment lifetime, an old duplicate could carry a sequence number that is valid again. RFC 9293 takes MSL as 2 minutes; with TCP timestamps, PAWS (protection against wrapped sequences, RFC 7323) rejects such old segments.

BandwidthWrap time
10 Mbpsabout 3436 s (57 min)
100 Mbpsabout 344 s
1 Gbpsabout 34.4 s
10 Gbpsabout 3.4 s

The inverse question: for bandwidth BB and lifetime LL, the sequence field needs at least ⌈lg⁡(BL/8)⌉\lceil \lg (B L / 8) \rceil bits to count every byte sent in LL seconds without repeating.

Worked examples

Example 1: a buffer trace

RcvBuffer = 4096 bytes, MSS = 1024 bytes, Clark's rule in use (threshold min⁡(1024,2048)=1024\min(1024, 2048) = 1024).

StepEventBufferedFreeAdvertised rwnd
0Connection opens040964096
13 segments arrive (3072 bytes)307210241024
2Application reads 1024204820482048
32 segments arrive (2048 bytes)409600 (sender stops; persist timer)
4Application reads 1 byte409510 (Clark: 1 < 1024); a naive receiver would say 1
5Application reads 1023 more307210241024

Between steps 3 and 5 the sender's persist timer may fire; the probe is answered with the current window (0 at step 4).

Example 2: how much may be sent

LastByteAcked = 5000, LastByteSent = 7000, the latest rwnd = 3000, and cwnd = 4000. The bound is min⁡(3000,4000)=3000\min(3000, 4000) = 3000 bytes outstanding; 2000 are outstanding, so 1000 more may be sent now: bytes 7001 to 8000.

Example 3: window needed and scale factor

A path with 1 Gbps and RTT 100 ms has bandwidth-delay product 109×0.1/8=12,500,00010^9 \times 0.1 / 8 = 12{,}500{,}000 bytes. Without scaling the window is at most 65,535 bytes, so throughput is at most about 5.24 Mbps, about 0.5% of the link. The least shift with 65,535⋅2S≥12,500,00065{,}535 \cdot 2^S \ge 12{,}500{,}000 is S=8S = 8 (272^7 gives 8,388,480; 282^8 gives 16,776,960).

Example 4: bits for the sequence number

A 1 Gbps link and a segment lifetime of 60 s: 109×60/8=7.5×10910^9 \times 60 / 8 = 7.5 \times 10^9 bytes can be sent in one lifetime. 232≈4.29×109<7.5×109≤2332^{32} \approx 4.29 \times 10^9 < 7.5 \times 10^9 \le 2^{33}, so at least 33 bits are needed; TCP's 32 bits wrap in 34.4 s at this rate, which is why PAWS exists.

Example 5: Nagle's algorithm

A user types a character every 70 ms (at 0, 70, ..., 980 ms; 15 characters) over a connection with RTT 200 ms. Without Nagle, 15 one-byte segments are sent. With Nagle, the first character goes at once; each later segment leaves when the previous one is acknowledged:

Sent at (ms)02004006008001000
Characters123333

Six segments instead of fifteen.

Example 6: the fastest rate without wraparound

With MSL 2 minutes, sequence numbers must not wrap within 120 s, so at most 2322^{32} bytes may be sent in 120 s:

B≤232×8120≈286,331,153 bit/s,B \le \frac{2^{32} \times 8}{120} \approx 286{,}331{,}153 \text{ bit/s},

about 286 Mbps. Faster connections rely on PAWS; at 1 Gbps the sequence space wraps in about 34.36 s.

Important properties and observations

  • Byte-based, variable window. Unlike link-layer sliding windows, the window is in bytes and set by the receiver segment by segment.
  • The receiver controls the right edge. ACKs move the left edge; reading by the application moves the right edge.
  • A receiver should not shrink the window (move the right edge back); RFC 9293 strongly discourages it.
  • Flow control is end to end. Routers take no part; congestion control deals with the network.

Common misconceptions.

  • "rwnd is the buffer size." It is the free part of the buffer.
  • "Flow control and congestion control are the same." One protects the receiver (rwnd), the other the network (cwnd); the sender uses the minimum.
  • "A zero window ends the connection." The sender waits and probes.
  • "Window scaling changes the sequence-number size." Sequence numbers stay 32 bits; only the window field is scaled.

GATE examination perspective

  • rwnd arithmetic. Buffer minus buffered bytes; new-send allowance = max⁡(0,min⁡(rwnd,cwnd)−FlightSize)\max(0,\min(\text{rwnd},\text{cwnd})-\text{FlightSize}); a reduction may leave older data above the new window.
  • Maximum throughput. Window / RTT; check the 65,535-byte limit without scaling.
  • Scale factor. Least S≤14S \le 14 with 65,535⋅2S≥65{,}535 \cdot 2^S \ge bandwidth-delay product.
  • Wraparound. 232×8/B2^{32} \times 8 / B seconds; compare with the lifetime; or bits = ⌈lg⁡(BL/8)⌉\lceil \lg (B L / 8) \rceil; or the largest rate 232×8/L2^{32} \times 8 / L for lifetime LL.
  • Silly window, Nagle, Clark, persist timer. Know which side each mechanism runs on.

Summary

Sources

  1. A. S. Tanenbaum, D. J. Wetherall, Computer Networks, 5th ed., Pearson, 2011: Sections 6.5.8 (TCP Sliding Window) and 6.6.6 (Protocols for Long Fat Networks).
  2. J. F. Kurose, K. W. Ross, Computer Networking: A Top-Down Approach, 8th ed., Pearson, 2021: Section 3.5.5 (Flow Control).
  3. B. A. Forouzan, Data Communications and Networking, 5th ed., McGraw-Hill, 2013: Chapter 24 (Internet Transport-Layer Protocols).
  4. D. Borman, B. Braden, V. Jacobson, R. Scheffenegger (Ed.), RFC 7323, TCP Extensions for High Performance, 2014, Sections 1.2 (TCP Reliability: wrapped sequence numbers), 2 (TCP Window Scale Option, shift limit 14) and 5 (PAWS), (consulted 2026-11-11).
  5. R. Braden (Ed.), RFC 1122, Requirements for Internet Hosts: Communication Layers, 1989, Sections 4.2.3.2 (When to Send an ACK Segment), 4.2.3.3 (When to Send a Window Update) and 4.2.3.4 (When to Send Data: Nagle), (consulted 2026-11-11).
  6. W. Eddy (Ed.), RFC 9293, Transmission Control Protocol (TCP), 2022, Sections 3.4 (Sequence Numbers: MSL of 2 minutes), 3.7.4 (Nagle Algorithm) and 3.8.6 (Managing the Window), (consulted 2026-11-11).

Next steps