Worked examples — TCP flow control — sliding window, receive buffer
This page drills the flow-control machinery from the parent topic until no scenario can surprise you. We take the two formulas you already met — the advertised window and the sender's budget — and run them through every corner case: normal, empty, full, zero, scaled, and a couple of exam traps.
Recall The only two formulas we lean on (built in the parent)
Advertised free space the receiver announces:
What the sender may still push out right now:
Every word here — RcvBuffer, LastByteRcvd, LastByteRead, LastByteSent, LastByteAcked — was defined in the parent. If any feels unfamiliar, re-read the parent's "WHAT is the receive buffer" section before continuing.
The scenario matrix
Before we solve anything, let us list every kind of situation these formulas can land in. Think of this as a checklist: by the end, every row has a worked example next to it, so you never meet a case you have not seen.
| Cell | Situation | What is special about it |
|---|---|---|
| A | Normal, room to spare | occupancy < buffer, in-flight < rwnd |
| B | Buffer empty (degenerate low) | LastByteRcvd = LastByteRead, rwnd = full buffer |
| C | Buffer exactly full (rwnd = 0) | receiver says "STOP" — the zero window |
| D | Window scaling (16-bit overflow) | true window bigger than , uses shift |
| E | In-flight equals rwnd (window saturated) | room to send = 0, but buffer NOT full |
| F | Zero-window probe recovery | rwnd goes positive, unsticking a stall |
| G | Real-world word problem | drain-rate vs fill-rate over time |
| H | Exam twist: min(rwnd, cwnd) | congestion window, not the buffer, is the bottleneck |
The subtle pair to keep separate is C vs E. In C the buffer is physically full. In E the buffer has free space but the sender is not allowed to use it yet because its earlier bytes are still unacknowledged in the network. Same "you can't send" result, totally different cause — we give each its own example.

Example 1 — Cell A: the ordinary healthy connection
Forecast: guess before computing — is the sender free to send a healthy chunk, or nearly stalled? (It "feels" healthy — small occupancy.)
- Occupancy .
Why this step? These are bytes that arrived but the app has not
read()yet — they sit taking up buffer space. - rwnd . Why this step? rwnd is the literal free space: total minus occupied.
- In-flight . Why this step? Those 800 bytes are on the wire, unacknowledged; they already have reserved seats in the buffer.
- Room to send . Why this step? We must not promise more room than exists, so subtract what is already claimed.
Verify: if the sender pushes all 5700, in-flight becomes . Exactly filling the advertised room without exceeding it. Units: all bytes. ✓
Example 2 — Cell B: the empty buffer (degenerate low input)
Forecast: the buffer is empty — does rwnd equal the full buffer, or zero?
- Occupancy . Why this step? Nothing accepted-but-unread means zero bytes occupy the buffer.
- rwnd . Why this step? All memory is free, so the receiver advertises the entire buffer.
Verify: occupancy can never be negative (you cannot read bytes you have not received, so LastByteRead ≤ LastByteRcvd). The maximum possible rwnd is RcvBuffer itself, reached exactly here. ✓ This is the upper limiting case — the mirror of Cell C.
Example 3 — Cell C: buffer exactly full, rwnd = 0 (limiting value)
Forecast: the app stopped reading — will rwnd be small-but-positive, or hit hard zero?
- Occupancy . Why this step? Standard occupancy count.
- rwnd . Why this step? Occupancy equals the whole buffer, so free space is nothing left.
- Consequence: the receiver advertises
Window = 0; the sender's rule forces it to send no new data. Why this step? Any new byte would have nowhere to land and would be dropped.
Verify: room to send . If in-flight is already (all acked), room to send : correct, the sender is frozen. Occupancy can never exceed RcvBuffer, so rwnd never goes negative — is the floor. ✓ This is the lower limiting case.
Example 4 — Cell E: window saturated but buffer NOT full
Forecast: the buffer still has 6500 free — surely the sender can send something? (Trap!)
- In-flight . Why this step? Count the unacknowledged bytes riding the network.
- Room to send . Why this step? The window is exactly full of in-flight bytes; every seat rwnd promised is already claimed by data in transit.
Verify: the buffer is not physically full — those 6500 bytes have not arrived yet, they are on the wire. The sender is blocked purely by the promise, not by real occupancy. Contrast with Example 3, where the buffer was genuinely full. When an ACK arrives, LastByteAcked rises, in-flight drops, and room reopens — the window slides. ✓
Example 5 — Cell F: zero-window probe recovery
Forecast: the app freed 3000 bytes — will the sender automatically find out?
- New occupancy . Why this step? 3000 fewer unread bytes now sit in the buffer.
- New rwnd . Why this step? Freed space becomes advertisable room again.
- The catch: the receiver only sends a segment when it has data or an ACK to send. With no data flowing, this fresh
rwnd = 3000might never leave. Why this step? This is the zero-window deadlock the parent warned about. - The fix: the sender periodically transmits a 1-byte zero-window probe. The receiver's reply carries the current
Window = 3000, unsticking the flow. Why this step? The probe forces the receiver to emit a fresh advertisement.
Verify: after recovery the sender may push new bytes (assuming nothing in flight). rwnd climbed from to — exactly the 3000 the app drained. Conservation holds: bytes freed = window regained. ✓
Example 6 — Cell D: window scaling past the 16-bit wall
Forecast: can a 16-bit field ever mean more than 65535? (Yes — via the shift multiplier.)
- Effective window formula: . Why this step? The scale option (built in the parent's Example 3) multiplies the raw field by instead of widening it — old TCPs stay compatible.
- Multiplier . Why this step? means "shift left 6 bits," i.e. multiply by 64.
- Raw Window . Why this step? Invert the formula: . And , so it fits the 16-bit field. ✓
- Sender interprets: bytes. Why this step? Confirms the receiver's intent survives the shift.
Verify: exactly, and so it is a legal field value. Without scaling, the ceiling would be bytes — about 18× too small for this link. ✓
Example 7 — Cell G: real-world drain-vs-fill over time
Forecast: net fill is fill minus drain — is it positive (buffer grows) or does the app keep up?
- Net fill rate bytes/s. Why this step? Occupancy changes at (bytes arriving) − (bytes read). Positive means the buffer is losing free space.
- Time to full s. Why this step? Starting empty, occupancy grows at 20000 B/s; it reaches 60000 B (full) after seconds.
- At s: occupancy , so . Why this step? The buffer is full → receiver advertises Window 0 → flow control brakes the sender down toward the app's 30000 B/s.
Verify: units — . ✓ Sanity: after the brake, sustained throughput cannot exceed the app's read rate of 30000 B/s — flow control matches sender speed to consumer speed, exactly its job. ✓
Example 8 — Cell H: the exam twist, min(rwnd, cwnd)
Forecast: rwnd is huge (40000) — does the sender get to use all of it? (Trap: two brakes, not one.)
- Effective window . Why this step? The sender must respect both limits: rwnd protects the receiver's buffer, cwnd protects the network. The tighter one wins. See TCP congestion control — cwnd, AIMD.
- Room to send . Why this step? Subtract in-flight from the binding window, exactly as in every other example — just with the effective window in place of rwnd.
Verify: had we naively used rwnd, we'd get — nearly 5× too much, flooding a lossy network. The bottleneck here is the network, not the receiver's buffer. Either window can bind; here cwnd does. ✓
Recall Matrix coverage check
A → Ex 1 · B → Ex 2 · C → Ex 3 · D → Ex 6 · E → Ex 4 · F → Ex 5 · G → Ex 7 · H → Ex 8. Every cell has a worked example. ✓
Recall
Occupancy formula and why it can't be negative
LastByteRcvd − LastByteRead; you can't read bytes you never received, so LastByteRead ≤ LastByteRcvd.Cell C vs Cell E — both give "0", what's the difference?
In Example 6, why does raw Window = 18750 with s = 6 mean 1.2 MB?
Example 7: net fill rate and time to full?
Example 8: why 7000 not 35000?
Connections
- 4.3.21 TCP flow control — sliding window, receive buffer (Hinglish)
- TCP congestion control — cwnd, AIMD
- Bandwidth-delay product
- Buffer overflow & packet loss
- TCP segment header format
- Stop-and-wait vs sliding window protocols
- TCP three-way handshake & options