Sliding window
Imagine a version of TCP that sent one segment, then waited for its acknowledgment before sending the next. It would work — every guarantee from Introduction to TCP would still hold — and it would be miserably slow on anything but a local network. Send a segment to a server 80 milliseconds away, wait for the ACK, send the next: that's 80 milliseconds of pure idle waiting for every single segment, no matter how fast the link actually is. The sliding window is the mechanism that lets TCP avoid this without giving up on ordered, acknowledged delivery, and it's the piece of TCP that both Flow control and Congestion control build directly on top of.
Stop-and-wait, and why it wastes a connection's real capacity
The problem with sending one segment and waiting isn't bandwidth, it's the round trip. A link might be capable of moving gigabits per second, but if the sender only ever has one segment in flight at a time, the connection's actual throughput is capped by how many round trips per second fit into the connection's latency — not by how much data the link could otherwise carry. A 1,460-byte segment over a connection with an 80ms round-trip time, sent one at a time, tops out around 18 KB/s, regardless of whether the underlying link is a 10 Mbps connection or a 10 Gbps one. The link's actual capacity is going almost entirely unused, sitting idle between each segment and its acknowledgment.
The fix is to allow multiple segments to be in flight — sent but not yet acknowledged — at the same time, so the round-trip cost is paid once for a whole batch of segments instead of once per segment.
The window as a moving range over the byte stream
TCP tracks a window: a contiguous range of the byte stream that's allowed to be in flight — sent, but not yet acknowledged. As acknowledgments arrive confirming the oldest unacknowledged bytes, the window slides forward, freeing up room to send new bytes at the leading edge without needing to wait for everything already in flight to be confirmed first.
Sent and acknowledged | ---- window: sent, unacknowledged ---- | Not yet sent
^ ^
window start window end
Walk through a small example with a window covering three segments' worth of data:
Step 1: send segments 1, 2, 3 (window covers all three, none acknowledged yet)
Step 2: ACK for segment 1 arrives → window slides forward by one segment
now segment 4 can be sent
Step 3: ACK for segment 2 arrives → window slides again
now segment 5 can be sent
Each acknowledgment doesn't just confirm one segment — because TCP acknowledgments are cumulative, an ACK for segment 2 confirms segment 1 as well, implicitly, even if that ACK was the only one that arrived. This matters when acknowledgments themselves are lost: if the ACK for segment 1 never arrives but the ACK for segment 2 does, the sender still knows segment 1 was received, because segment 2's ACK could only have been generated after segment 1 arrived in order. It's a small design choice that quietly absorbs a whole class of lost-ACK scenarios without any extra retransmission logic.
What decides how wide the window can be
Two entirely separate limits cap how much data may be in flight at once, and TCP always uses whichever is smaller at any given moment:
- The receiver's advertised window — how much buffer space the receiving application has free right now. This is Flow control's job to manage and communicate, and it protects the receiver from being overwhelmed.
- The sender's congestion window — the sender's own estimate of how much the network path in between can absorb without inducing loss. This is Congestion control's job, and it protects the network, not either endpoint directly.
Introduction to TCP already flagged why these are tracked separately rather than as one number: a fast receiver on a congested network path, and a slow receiver on an uncongested one, are different problems with different fixes, and conflating them would mean reacting to the wrong signal. The sliding window itself is the shared mechanism both limits operate through — it's the thing being constrained, not the constraint.
Out-of-order arrival and selective acknowledgment
IP packets carrying TCP segments don't all take the same path, and they can arrive out of order even when nothing was actually lost. Base TCP's acknowledgment scheme, being purely cumulative, has an awkward answer for this: if segment 3 arrives before segment 2, the receiver can't acknowledge segment 3 yet, because a cumulative ACK for segment 3 would falsely claim segment 2 arrived too. All the receiver can do is keep re-acknowledging segment 1 (the last one it can confirm in full sequence) until the missing segment 2 shows up — which tells the sender something is outstanding, but not precisely what.
Selective Acknowledgment (SACK), negotiated as a TCP option during the handshake (visible as sackOK in the capture shown in Three-way handshake), fixes this: it lets the receiver report the specific, non-contiguous ranges of data it has actually received, even with gaps in between. A sender that supports SACK can then retransmit precisely the missing range instead of guessing or resending everything from the point of the gap onward. Almost every modern TCP stack negotiates SACK by default, and its absence — visible in a packet capture as a handshake with no sackOK option on either side — is itself a diagnostic clue when a connection is retransmitting more data than a given loss event should require.
Reading a window in a packet capture
The window value itself is visible directly in a capture:
11:40:02.001004 IP 10.0.5.12.53980 > 93.184.216.34.443: Flags [P.], seq 1:1401, ack 1, win 2053, length 1400
11:40:02.041220 IP 93.184.216.34.443 > 10.0.5.12.53980: Flags [.], ack 1401, win 1026, length 0
11:40:02.041980 IP 10.0.5.12.53980 > 93.184.216.34.443: Flags [P.], seq 1401:2801, ack 1, win 2053, length 1400
11:40:02.083310 IP 93.184.216.34.443 > 10.0.5.12.53980: Flags [.], ack 2801, win 1026, length 0
The win value here is the raw 16-bit window field, and if window scaling was negotiated during the handshake (the wscale option shown in Three-way handshake), the real advertised window is this number multiplied by 2 raised to the negotiated scale factor — a detail worth knowing before assuming win 1026 means a literal 1,026-byte window, since with even a modest scale factor that's actually well over 100 KB. Reading the exchange: the client sends 1,400 bytes of data (seq 1:1401), the server acknowledges receipt (ack 1401) while separately reporting its own receive window, and the pattern repeats for the next segment. Nothing here is waiting on a full round trip per segment — the client's window (win 2053, scaled) is wide enough that multiple segments could be outstanding simultaneously if the application had more to send at once.
Practice exercises
- Using the cumulative-ACK explanation above, work through this sequence: a sender transmits segments 1 through 5; the ACK for segment 3 is lost in transit, but the ACK for segment 4 arrives normally. Explain whether the sender needs to retransmit segment 3, and why.
- A connection's packet capture shows the client repeatedly re-sending the same ACK value for several segments in a row, each one acknowledging the same sequence number. Using this article's explanation of out-of-order arrival, explain what's most likely happening on the receiver's side.
- Calculate, roughly, the maximum throughput a stop-and-wait scheme (one segment, then wait for its ACK, repeat) could achieve on a connection with a 120ms round-trip time and a 1,460-byte maximum segment size — then explain in one sentence why widening the window is the only way to raise that ceiling without reducing the round-trip time itself.
Knowing how the window slides explains the mechanism, but not yet either of the two forces that decide how wide it's allowed to get. Flow control picks up the first of those — the limit set by the receiver's own buffer, and how a receiver actually communicates it mid-connection as conditions change.
Sources
- IETF, RFC 9293 – Transmission Control Protocol (TCP) — defines cumulative acknowledgment and the window field.
- IETF, RFC 2018 – TCP Selective Acknowledgment Options