Congestion Control: Slow Start, AIMD and Fast Recovery

Transport Layer · 30 min

Core CS · Computer Networks

Nobody tells TCP how fast to send, so it finds out by losing

A router will not warn you that its queue is full. It drops your segment and tells nobody. TCP turns that silence into a control loop: raise the window until something disappears, halve it, and start climbing again. Four rules, two loss signals, and one sawtooth that every interviewer wants you to draw.

Step fifteen round trips of cwnd, Reno against Tahoe
Flow control asks the receiver how much it can take, and the receiver answers in every ACK. Congestion control asks the network how much it can take, and the network never answers at all. So TCP raises its own window until a segment goes missing, and treats that loss as the reply.

01 The idea

The receiver is not the problem here

Flow control and congestion control both slow the sender down, and that is the only thing they share. Flow control protects the receiver: it stops a fast sender from overrunning a slow receiver’s buffer, and the receiver states its remaining space explicitly in the 16-bit window field of every ACK it sends. Congestion control protects the network in between: it stops all the senders together from overrunning the routers on the path, and no header anywhere carries a number the sender can read off, because no router on the path knows what your share ought to be. There is one narrow exception, a single congestion mark called ECN, and the end of section 03 covers it and why you still cannot rely on it. Keep those two apart and a third of this topic is already done.

So what is congestion, physically? A router has an outgoing link and a queue in front of it. Packets arrive from several inputs and leave on one link. If they arrive faster than the link can carry them away, the queue grows. That is the first symptom and it is not loss at all: it is delay, because every packet now waits behind the ones already queued. The queue is finite memory. Once it is full the router has nothing left to do with the next arrival but drop it. Nothing has failed and nobody is at fault; a perfectly healthy router discarding packets is what an overloaded network looks like.

Now add the thing that makes it dangerous. Every dropped segment is a segment TCP will retransmit, and a retransmission is more traffic offered to the same overloaded router. More offered load means a longer queue, which means more drops, which means more retransmissions. The link stays one hundred per cent busy while the useful throughput, the goodput, falls toward zero, because most of what the link carries is copies of things it already carried. That runaway is congestion collapse, and it is not a thought experiment. In October 1986 the path between Lawrence Berkeley Lab and UC Berkeley, a few hundred metres and three hops, fell from 32 kbit/s to 40 bit/s. Van Jacobson’s answer to that collapse became slow start and congestion avoidance, and they went into TCP in 1988.

Here is the hard constraint that shapes everything after. The network gives the sender no signal. IP is best effort: the router that dropped your segment sends nothing back, the routers upstream of it do not know it happened, and the receiver cannot report a segment it never saw. So TCP watches the only thing it can actually observe, which is whether its own segments are being acknowledged, and it makes one assumption: a lost segment means a full queue. That assumption was true of the wired links TCP was designed for and it is the whole basis of the mechanism. It is also the mechanism’s honest weakness. On a Wi-Fi or mobile link a lost frame usually means interference or a fade, not a full router queue, and TCP halves its window for a reason that does not exist. Say that out loud in an interview; it is the difference between reciting the rules and understanding them.

What TCP raises and lowers is the congestion window, cwnd. It is a variable held only at the sender. It is not advertised, it is not in any header, and the receiver never learns its value. It is the sender’s own running estimate of how much unacknowledged data the path can hold. The sender is then bounded by two limits at once and must respect the smaller: it may have at most min(cwnd, rwnd) bytes in flight, where rwnd is the receiver’s advertised window. One number protects the far machine, the other protects everything between you and it.

Congestion control is the sender guessing, on its own, how much data the path can hold. It starts at one segment and doubles every round trip until it hits its own threshold or something breaks, then creeps up by one segment per round trip and halves whenever a segment is lost. Loss is not a failure to be reported; loss is the report.
Congestion window, cwndThe sender’s self-imposed limit on unacknowledged data, held only at the sender and measured in bytes, though it is almost always spoken about in units of MSS. Nothing on the network can read it or set it. It is the one variable this entire lesson moves up and down.
Slow start threshold, ssthreshThe value of cwnd at which growth changes gear from doubling every round trip to adding one segment per round trip. It is the sender’s memory of the last window that got it into trouble, which is why every loss sets it to half of the current cwnd.
MSS and the effective windowMSS is the largest payload one TCP segment carries: 1460 bytes on a 1500-byte Ethernet MTU, once the 20-byte IPv4 header and the 20-byte TCP header are taken off. The sender may have min(cwnd, rwnd) bytes outstanding, so the tighter of the two windows is the one in force.

02 Worked example

Fifteen round trips, one dropped segment and one timeout

One run, and it is the same run everywhere else in this lesson: the console in section 04 loads it as its first preset, and the round-by-round table in section 03 is this run written out for both TCP versions. The sender opens with cwnd = 1 MSS and ssthresh = 8 MSS. Two things go wrong on purpose, and they are deliberately the two different kinds of wrong: three duplicate ACKs at the end of round 8, and the retransmission timer expiring at the end of round 11. Everything below is TCP Reno. Read left to right.

1 · Start at oneRound 1. cwnd = 1 MSS, so one segment goes out and the sender waits a full round trip for its ACK. ssthresh = 8 is the ceiling on the doubling.
2 · Double, four timesSlow start. Each ACK raises cwnd by 1 MSS, and a window of c segments returns c ACKs, so cwnd doubles per round trip: 1, 2, 4, 8. At 8 it equals ssthresh, so slow start is over.
3 · Add one, four timesCongestion avoidance. cwnd now rises by exactly 1 MSS per round trip: 9, 10, 11, 12. This is the additive increase of AIMD, and it is deliberately timid because the sender is near a limit it cannot see.
4 · Three duplicate ACKsEnd of round 8, cwnd = 12. Fast retransmit sends the missing segment at once. Fast recovery sets ssthresh = 12 / 2 = 6 and cwnd = 6. Round 9 restarts at 6, not at 1.
5 · Then a timeoutRounds 9 to 11 climb 6, 7, 8. At the end of round 11 the timer expires: ssthresh = 8 / 2 = 4, cwnd = 1. Slow start again, 1, 2, 4, then 4 meets ssthresh and round 15 is 5.

Written out, Reno gives 1, 2, 4, 8, 9, 10, 11, 12, 6, 7, 8, 1, 2, 4, 5. Add them and the sender has put 90 MSS on the wire across those fifteen round trips. Run the identical loss script under TCP Tahoe instead and you get 1, 2, 4, 8, 9, 10, 11, 12, 1, 2, 4, 1, 2, 3, 4, which totals 74 MSS. The first eight rounds are byte for byte the same, 57 MSS each. The entire 16 MSS gap opens at one moment, the triple duplicate ACK at the end of round 8, and that single divergence is the difference between the two versions.

Slow start is the part of this that gets misread, so fix it now. It is exponential. From 1 MSS it takes ten round trips to reach a window of 1024 MSS, which on a 1460-byte MSS is about 1.5 MB in flight. Nothing about that is slow. The name is historical and it is comparative: before 1988 a TCP sender opened a connection by dumping an entire receiver window onto the path in one burst, and against that, starting from a single segment is slow. Slow start is slow only at the very beginning, and only there.

The highlighted node is the one to sit with, because it is where the two loss signals stop being interchangeable. A duplicate ACK is what a receiver sends when a segment arrives out of order: it re-acknowledges the last in-order byte it has, which is a way of saying “something arrived, but not the piece I am waiting for”. Three of them therefore prove that at least three segments sent after the missing one got all the way across. The path is working. One segment fell out of it. Halving the window is a proportionate response, and waiting for a timer that may be hundreds of milliseconds away would be a waste of a working path.

A timeout is the opposite piece of evidence, and it is evidence by absence. The retransmission timer expired and nothing came back, not a duplicate ACK, not a new ACK, nothing. The sender has lost its ACK clock entirely and no longer has any idea what the path can carry, or whether there is a path. Treating that as severe and dropping to a single segment is not pessimism; it is the sender admitting it has no information left. Everything in section 03 is a formal statement of the difference between those two paragraphs.

03 Mechanics

The four phases, the two loss signals and the two versions

Four tables, in the order the questions arrive. First the phases as a state table, because that is the shape of the question: an interviewer names a state and asks what the rule is and what leaves it. Every rule below is stated per round trip, which is the unit that matters, and the per-ACK form is given alongside because that is what the code actually does.

PhaseEntered whenWhat happens to cwndLeft when
Slow start The connection opens; after any timeout; after three duplicate ACKs under Tahoe. cwnd += 1 MSS per ACK, so cwnd doubles per RTT — exponential. cwnd reaches ssthresh, or a loss is detected.
Congestion avoidance cwnd reaches ssthresh; or, under Reno, on leaving fast recovery. cwnd += 1 MSS per RTT, coded as cwnd += MSS × MSS / cwnd per ACK — linear. A loss is detected, by either signal.
Fast retransmit Three duplicate ACKs arrive for the same sequence number. Nothing yet. The missing segment is resent immediately, without waiting for the timer. At once, into fast recovery under Reno or into slow start under Tahoe.
Fast recovery (Reno only) Straight after fast retransmit. ssthresh = cwnd / 2, then cwnd = ssthresh + 3 MSS while recovering. The ACK for the retransmitted segment arrives; cwnd deflates to ssthresh and congestion avoidance resumes.

Where that + 3 comes from, and why exams still want cwnd / 2. cwnd is a limit on data in the network. Three duplicate ACKs are three pieces of proof that three segments have left the network and reached the receiver, so the sender is entitled to put three more in without exceeding its new limit. That is the entire justification for the inflation, and each further duplicate ACK adds one more MSS for exactly the same reason. When the ACK for the retransmitted segment finally arrives, the window is deflated back to ssthresh. Written answers almost always want the deflated value, so the safe reply is cwnd = ssthresh = old cwnd / 2, with the inflation named in one clause to show you know why the number moves.

Now the two signals side by side. This is the single most examined table in the topic, because the exam question is nearly always “which of the two happened, and what does each version do about it”.

SignalWhat TCP infersNew ssthreshTahoe cwndReno cwndNext phase
Retransmission timeout severe: nothing at all is getting through max(cwnd / 2, 2 MSS) 1 MSS 1 MSS Slow start, in both versions.
Three duplicate ACKs mild: one segment lost, later ones arriving max(cwnd / 2, 2 MSS) 1 MSS ssthresh, i.e. half Tahoe: slow start. Reno: congestion avoidance.

Two details that get dropped. First, ssthresh is never allowed below 2 MSS: RFC 5681 defines it as max(FlightSize / 2, 2 × SMSS), so repeated losses on a tiny window cannot drive it to zero and strand the connection. Second, both versions have fast retransmit. Tahoe introduced it in 1988 and it is not what Reno added. Reno added fast recovery, which is the decision to halve rather than collapse afterwards. Saying “Tahoe has no fast retransmit” is the standard slip and it is worth a mark.

Here is the section 02 run written out for both versions on the identical loss script, which is what the console’s two tabs step through. The two columns are the same for eight rounds and then part company at one row. Every value follows from the two tables above and nothing else.

RoundEvent at the end of this roundReno — cwnd / ssthresh / phaseTahoe — cwnd / ssthresh / phase
11 / 8 / slow start1 / 8 / slow start
22 / 8 / slow start2 / 8 / slow start
34 / 8 / slow start4 / 8 / slow start
4cwnd has reached ssthresh8 / 8 / cong. avoidance8 / 8 / cong. avoidance
59 / 8 / cong. avoidance9 / 8 / cong. avoidance
610 / 8 / cong. avoidance10 / 8 / cong. avoidance
711 / 8 / cong. avoidance11 / 8 / cong. avoidance
8three duplicate ACKs12 / 8 / cong. avoidance12 / 8 / cong. avoidance
9the versions diverge here6 / 6 / cong. avoidance1 / 6 / slow start
107 / 6 / cong. avoidance2 / 6 / slow start
11retransmission timeout8 / 6 / cong. avoidance4 / 6 / slow start
121 / 4 / slow start1 / 2 / slow start
13Tahoe reaches its ssthresh of 22 / 4 / slow start2 / 2 / cong. avoidance
14Reno reaches its ssthresh of 44 / 4 / cong. avoidance3 / 2 / cong. avoidance
155 / 4 / cong. avoidance4 / 2 / cong. avoidance
Total delivered over 15 round trips90 MSS74 MSS

Read row 11 carefully, because it is the one that punishes memorisation. The timeout fires at the same round in both columns, but it does not halve the same number. Reno is sitting at cwnd = 8 and gets ssthresh = 4; Tahoe is only at cwnd = 4, because it spent rounds 9 and 10 climbing back from one, and gets ssthresh = 2. The version that recovered better from the first loss also has more to lose in the second, and it still ends ahead. A loss rule always applies to whatever cwnd happens to be at that instant, never to some remembered earlier value.

Finally the versions, which is a question in its own right and comes up as “which TCP are you describing?”. Everything after Reno keeps the same two signals and changes only the growth curve or the bookkeeping.

VersionOn three duplicate ACKsOn a timeoutWhat it added
Tahoe 1988 fast retransmit, then ssthresh = cwnd/2 and cwnd = 1, slow start ssthresh = cwnd/2, cwnd = 1, slow start Slow start, congestion avoidance and fast retransmit. The original three.
Reno 1990 fast retransmit, then fast recovery: ssthresh = cwnd/2 and cwnd = ssthresh identical to Tahoe Fast recovery. One rule, and it is the whole examinable difference.
NewReno RFC 6582 as Reno, but stays in fast recovery until every segment outstanding at the loss is acknowledged identical to Reno Correct handling of more than one loss in the same window, which plain Reno mishandles.
CUBIC Linux default same signal, but growth is a cubic function of time since the last loss identical in kind Growth that scales on long fat paths, where one MSS per round trip is far too slow to fill the pipe.

Why AIMD converges on a fair share, and why that is a property of the decrease. Put two flows with the same round trip time through one bottleneck. Additive increase adds the same 1 MSS per round trip to both windows, so it moves them up together and leaves the difference between them exactly as it was. Multiplicative decrease halves both windows, and halving two numbers halves the gap between them too. Repeat that cycle and the gap is driven toward zero while the sum stays pinned near the capacity of the link, so the two flows converge on an equal share without ever exchanging a single message about it. The increase rule keeps the link busy; the decrease rule is where the fairness lives.

Two windows, and the smaller one wins. A sender may have at most min(cwnd, rwnd) bytes unacknowledged. rwnd arrives from the receiver in a 16-bit field, so it tops out at 65,535 bytes unless the two ends negotiated the window scale option from RFC 7323 during the handshake, which multiplies it by a power of two. On a fast modern path the binding limit is almost always cwnd, and a question that gives you both numbers is checking whether you take the minimum or quietly ignore the one you were not thinking about.

And the modern alternative to inferring anything. Explicit Congestion Notification, RFC 3168, lets a router that is about to build a queue mark a packet instead of dropping it, using two bits in the IP header, after which the receiver echoes the mark back to the sender in the ECE flag of its next ACK. The sender then reduces its window exactly as though a segment had been lost, except that nothing was lost and no round trip was spent recovering. It has to be enabled on hosts and on the routers in between, which is why loss is still the signal you will be asked about.

05 Cheat sheet

Twelve answers to have ready on the morning

Every row is something you can be asked to state in under ten seconds. The right-hand column is the specific wrong answer that gets written down, not a general caution.

What they askThe answerThe trap
cwnd when a connection opens1 MSS in the textbookClaiming a real Linux box also starts at 1. It starts at 10, per RFC 6928.
Slow start growth rate+1 MSS per ACK, so cwnd doubles every RTTcalling it slow — it is exponential and it is the fastest phase there is
Slow start ends whencwnd reaches ssthresh, or a loss is detectedwaiting for cwnd to exceed ssthresh — reaching it is enough
Congestion avoidance growth rate+1 MSS per RTTAdding 1 MSS per ACK. That is slow start again, under the wrong name.
On a timeoutssthresh = cwnd / 2, cwnd = 1 MSS, slow starthalving cwnd — a timeout collapses it, it does not halve it
On three duplicate ACKs, Renossthresh = cwnd / 2, cwnd = ssthresh, congestion avoidanceresetting cwnd to 1 — that is Tahoe’s answer, not Reno’s
On three duplicate ACKs, Tahoefast retransmit, then ssthresh = cwnd / 2, cwnd = 1, slow startSaying Tahoe has no fast retransmit. It has. It has no fast recovery.
Floor on ssthreshnever below 2 MSS (RFC 5681)Halving repeatedly down to 1 or 0 and stranding the connection.
How much may be in flightmin(cwnd, rwnd)quoting cwnd alone — flow control is still running underneath
The two halves of AIMDadditive increase +1 MSS/RTT; multiplicative decrease × 1/2Putting the additive increase in slow start. It belongs to congestion avoidance.
Why loss and not a messageIP is best effort; the router that drops has no way to tell the sendersaying the router informs the sender — only ECN does, and only if enabled
Where the loss assumption failswireless: a lost frame means interference, not a full queueClaiming TCP behaves identically over Wi-Fi. It halves for no reason.
Two windows, and you obey the smallerThe receiver’s rwnd protects one machine’s buffer and the sender’s cwnd protects every router in between. They are computed in completely different places by completely different means, and the sender is bound by min(cwnd, rwnd) at every instant.
One phase doubles, the other adds oneAll of TCP’s growth is those two rules and a single threshold that decides which is running. If you can say which phase you are in, you can say what the next round’s cwnd is without knowing anything else about the connection.
Halve for a dup ACK, collapse for a timeoutBoth signals halve ssthresh, and that part is identical. They differ only in what happens to cwnd: three duplicate ACKs prove the path still delivers, a timeout proves nothing is coming back at all.

06 Where & why

The same two variables, in four real stacks

None of this is a teaching abstraction. cwnd and ssthresh are fields you can print on a running socket, and the version in use is a string you can change at runtime. Each of the four systems below departs from the textbook in one specific way, and naming that departure is what separates a memorised answer from a used one.

Linux · ss and sysctl
The lesson, printed live for one socket

ss -ti prints cwnd: and ssthresh: for every established TCP connection, alongside the measured RTT, so the curve in section 04 is something you can watch on your own machine during a large download. sysctl net.ipv4.tcp_congestion_control shows which algorithm is in use and will say cubic, not reno, on any default install. One real difference to name: Linux opens at an initial window of 10 MSS, following RFC 6928, because on a modern path starting from one segment wastes several round trips on transfers that are over in a few.

Google · BBR on YouTube
Stops treating loss as the signal at all

BBR was built at Google and merged into Linux in version 4.9. Instead of inferring congestion from loss, it repeatedly measures the bottleneck bandwidth and the minimum round trip time and paces its sending to the product of the two, so it aims at keeping the pipe full without filling the queue. That is a direct answer to the two weaknesses named in section 01: it does not collapse on wireless loss that is not congestion, and it does not need a full buffer before it reacts. It is deployed on Google.com and YouTube, which is why it is a fair thing to be asked about.

Wi-Fi 6 · 802.11ax
Why the wireless problem is smaller than it sounds

The honest caveat in section 01 is real, but the link layer hides most of it. 802.11 retransmits a corrupted frame at the MAC layer, several times, before it ever gives up, so a burst of interference usually costs TCP some added delay rather than a visible loss. What TCP does see instead is a round trip time that swings wildly, which inflates the retransmission timer and makes genuine timeouts rare but slow. The classic exam answer, that TCP halves its window over Wi-Fi for no reason, is right in principle and overstated in practice, and saying both halves is the stronger answer.

Cloudflare · QUIC and HTTP/3
The same algorithm, moved out of the kernel

QUIC runs over UDP, so its congestion control lives in the application’s own library rather than in the operating system. RFC 9002 specifies a NewReno-style controller as the default and explicitly allows others, so Cloudflare and Google ship CUBIC and BBR inside the QUIC stack itself and can change them without touching a kernel. QUIC also acknowledges explicit packet-number ranges rather than one cumulative byte offset, which lets a sender see exactly which packets were lost and removes the ambiguity that forced NewReno to exist.

The reason the Internet does not collapse under load is not a router, a queue policy or a protocol field. It is that a few billion senders each independently agree to back off when a segment goes missing. Additive increase keeps the links busy, multiplicative decrease shares them out, and there is no authority anywhere enforcing either.

07 Interview questions

What they ask, and where they push

The reliable pattern here is that they ask for the rules, you give them, and then they name a state and a signal and want the next three values. Say which phase you are in before you give a number; a bare number with no phase attached reads as memorisation, and the follow-up will catch it.

What is congestion control, and how is it different from flow control?
Congestion control stops all the senders together from overrunning the routers on the path; flow control stops one sender from overrunning the receiver’s buffer. The difference that matters is who tells you. The receiver states its remaining space explicitly in the window field of every ACK, so flow control is a negotiation. No router can tell you anything, so congestion control is a guess the sender makes on its own and corrects when a segment goes missing. The sender obeys both at once and is bounded by min(cwnd, rwnd).
What is actually happening in the network when we say it is congested, and what is congestion collapse?
Packets are arriving at a router faster than its outgoing link can carry them, so the queue in front of that link grows. The first symptom is delay, because everything now waits behind what is already queued. The queue is finite, so once it fills the router drops arrivals. Congestion collapse is the runaway that follows: every drop is retransmitted, every retransmission is extra load on the same router, which causes more drops. The link stays fully busy while useful throughput falls toward zero, because most of what it carries is copies. It happened for real in 1986 on the Berkeley path, which fell from 32 kbit/s to 40 bit/s.
The network never tells the sender it is congested. So how does TCP find out?
It infers it, and the thing it infers from is loss. IP is best effort, so the router that dropped the segment sends nothing back and the receiver cannot report what it never saw. The only observable the sender has is whether its own segments are being acknowledged, so TCP treats a missing acknowledgement as evidence of a full queue and reduces its window. That inference is an assumption about the link, not a fact, and it is why a lossy wireless hop makes TCP back off for a reason that does not exist.
Walk me through slow start. And if cwnd doubles every round trip, why is it called slow?
It starts at cwnd = 1 MSS and adds 1 MSS for every ACK received. A window of c segments produces c ACKs, so cwnd doubles every round trip, which is exponential and reaches a window of 1024 MSS in ten round trips. It runs until cwnd reaches ssthresh or a loss is detected, whichever comes first. The name is historical and comparative: before 1988 a sender opened by dumping an entire receiver window onto the path at once, and against that, beginning from one segment is slow. It is slow only at the start.
What is AIMD, and which phase of TCP is it?
Additive Increase, Multiplicative Decrease. The additive increase is congestion avoidance: cwnd rises by exactly 1 MSS per round trip, implemented as cwnd += MSS × MSS / cwnd on each ACK so that a full window of ACKs adds one segment. The multiplicative decrease is what happens on any loss: ssthresh is set to half the current cwnd. Slow start is not the additive half, which is the usual mix-up; slow start is exponential and sits before AIMD ever begins.
You get three duplicate ACKs. What do you do, and why not wait for the timer instead?
Retransmit the missing segment immediately. That is fast retransmit, and then under Reno fast recovery sets ssthresh = cwnd / 2 and brings cwnd down to that rather than to 1. Waiting would waste a working path: a duplicate ACK is what a receiver sends when a segment arrives out of order, so three of them are proof that at least three segments sent after the missing one arrived. The retransmission timer, by contrast, is deliberately conservative and can be hundreds of milliseconds away, so waiting for it would idle the connection for no new information.
Why does a timeout reset cwnd to 1 when three duplicate ACKs only halve it?
Because they are different amounts of evidence. Three duplicate ACKs mean segments are still arriving, so one segment was lost on a path that otherwise works, and halving is proportionate. A timeout means the timer expired with nothing coming back at all, not a duplicate ACK, not a new ACK. The sender has lost its ACK clock and has no current information about the path, so it restarts from a single segment and rebuilds the estimate. Both cases halve ssthresh identically; they differ only in what happens to cwnd.
Tahoe or Reno, what exactly is the difference?
One rule: fast recovery, which Reno added in 1990. On three duplicate ACKs Tahoe does fast retransmit and then sets ssthresh = cwnd / 2 and cwnd = 1 and re-enters slow start. Reno does fast retransmit and then sets ssthresh = cwnd / 2 and cwnd = ssthresh and stays in congestion avoidance. On a timeout they are identical, both collapsing to 1. The common error is saying Tahoe has no fast retransmit; it introduced it. What it lacks is the recovery step afterwards.
A sender has a congestion window and the receiver has advertised a window. How does it decide how much to send?
It takes the minimum and never exceeds it: at most min(cwnd, rwnd) bytes may be unacknowledged at any moment. The two limits protect different things and are computed in different places, so neither substitutes for the other, and a sender that respected only cwnd would happily overrun a slow receiver. rwnd arrives in a 16-bit header field, so it caps at 65,535 bytes unless the window scale option from RFC 7323 was negotiated during the handshake. On a fast modern path the binding constraint is usually cwnd.
Two flows share one bottleneck but one has a much shorter round trip time. Who gets more, and why?
The short-RTT flow, and by roughly the ratio of the two round trip times. Both flows add 1 MSS per round trip during congestion avoidance, but the short-RTT flow completes more round trips per second, so it adds capacity faster in wall-clock terms and climbs back faster after every halving. Throughput ends up roughly inversely proportional to RTT. It is a real and well-known unfairness in TCP, not a misconfiguration, and it is one of the things newer controllers such as BBR set out to reduce.
TCP was designed for wired links. What goes wrong over Wi-Fi or a mobile network?
The core inference breaks. TCP reads loss as “a router queue is full”, but on a radio link a lost frame usually means interference or a fade, so the sender halves a window that was never too large and throughput drops for no reason. In practice the damage is smaller than the theory suggests, because 802.11 retransmits corrupted frames at the MAC layer before TCP ever sees a loss; what TCP sees instead is a round trip time that swings, which inflates the retransmission timer. The honest full answer names both the flaw and the mitigation, and then names BBR, which measures bandwidth and delay instead of inferring from loss.
Do you actually deal with any of this in a real job?
You rarely implement it and you read it constantly. ss -ti prints cwnd and ssthresh live for any socket, which is how you tell a slow transfer caused by the network from one caused by the application. Two real-world differences are worth naming: Linux opens at 10 MSS rather than 1, following RFC 6928, and its default algorithm is CUBIC rather than Reno, so the growth curve you see is not the one in the textbook. What the textbook version buys you is the vocabulary to explain why a transfer over a satellite link never reaches full speed, and interviews test it because it is the fastest proof that you understand feedback control in a system with no feedback channel.

08 Practice problems

Six to work on paper

Wherever a problem runs over rounds, write out one line per round trip rather than jumping to the pattern. Two of the six turn on applying a rule to the cwnd that is actually in force rather than the one you remember, and one of them cannot be finished without noticing what a cumulative acknowledgement is unable to say.

Eight rounds, no loss

Easy
A connection opens with cwnd = 1 MSS and ssthresh = 16 MSS and loses nothing. Give cwnd at the start of each of the first eight round trips, name the round in which congestion avoidance takes over, and give the total number of segments the sender has put on the wire across those eight rounds.
Follow-up
The phase changes in a round where cwnd is still an exact power of two, which makes it easy to place the switch one round late. The total is also not eight times anything: the only way to get it is to add the eight values you wrote down.
Show the hint
Write the eight values in a column and stop doubling at the moment cwnd equals ssthresh rather than the moment it passes it.

Read the signal

Easy
A Reno sender is in congestion avoidance with cwnd = 16 MSS and ssthresh = 8 MSS. Starting from that same state each time, give the new cwnd, the new ssthresh and the phase in force for the next round after each of: (a) the retransmission timer expires; (b) three duplicate ACKs arrive; (c) two duplicate ACKs arrive and then an ACK for new data; (d) an ACK for new data arrives.
Follow-up
Two of the four leave cwnd where it was or move it up, and one of those two looks exactly like the opening of the case that halves it. The threshold is three, and two is not three.
Show the hint
For each event ask what the sender is entitled to conclude about the network from that event alone, before you touch either variable.

Twelve rounds, two versions

Medium
A connection opens with cwnd = 1 MSS and ssthresh = 32 MSS. Three duplicate ACKs arrive at the end of round 6 and the retransmission timer expires at the end of round 10. Give cwnd for rounds 7 and 11 under both Tahoe and Reno, and the total segments each version delivers over the twelve rounds.
Follow-up
The two versions differ by a factor of sixteen at round 7 and are back to the same value at round 11, and the reason is not that Tahoe caught up. Their ssthresh values at round 11 are different, which is where the advantage actually survives.
Show the hint
Apply each loss rule to whatever cwnd that version happens to be sitting at when the event fires, not to the value the other version has.

How many round trips to fill the pipe

Medium
A path runs at 10 Mbit/s with a round trip time of 40 ms, and the MSS is 1250 bytes. Give the bandwidth-delay product in bytes and in segments, the first round trip in which slow start reaches or exceeds it starting from cwnd = 1 MSS with a large ssthresh, and how many milliseconds pass between the first segment leaving and that round beginning.
Follow-up
The rate is quoted in bits and the segment in bytes, and mixing them changes the answer by a factor of eight. The window also never lands exactly on the pipe size, so the answer to the second part is the first value that clears it, not the one that equals it.
Show the hint
Convert everything to one unit before you divide, and remember that cwnd only takes powers of two for as long as slow start is running.

Why the decrease does the work

Medium
Two flows share one bottleneck and have equal round trip times. Flow A is at cwnd = 30 MSS and flow B at cwnd = 10 MSS, both in congestion avoidance. There are exactly four round trips of additive increase between loss events, and every loss event halves both windows, rounding down. Give both windows immediately after each of the first three loss events. Then say what would happen instead if TCP subtracted a fixed 5 MSS on a loss rather than halving.
Follow-up
Halving rounds down, so the two windows close on each other over the three cycles without ever landing on the same value. The last part changes exactly one of the two AIMD rules and leaves the other untouched, which is the cleanest way to find out which of them the behaviour was coming from.
Show the hint
Do not track the two windows. Track the difference between them, and see what each of the two rules does to that difference.

One loss, then two

Hard
A Reno sender is in congestion avoidance with cwnd = 20 MSS on a path with a 100 ms round trip time and a retransmission timeout of 1 second. Exactly one segment is lost. (a) Roughly how long after the loss does fast retransmit act, and how long would a timeout have taken? (b) Give cwnd immediately after recovery under Reno and under Tahoe. (c) Now suppose two segments in that same window are lost. Say what Reno does when the ACK for the first retransmission arrives, and why that costs it a second loss event.
Follow-up
Part (c) is where the version that looked better in part (b) stops looking better, and the reason is a property of a cumulative acknowledgement rather than anything in the congestion rules. Naming that property is most of the answer.
Show the hint
Write down exactly which byte the receiver’s cumulative ACK is able to advance to once the first retransmission arrives while a later segment is still missing, and ask what Reno concludes from an ACK that advances at all.