1. Home
  2. Computer Networks
  3. TCP Congestion Control

TCP Congestion Control

TCP probes for the right speed. The window doubles in slow start, grows by one per round in congestion avoidance, and backs off after a loss.

Interactive 3DIntermediate12 min readCNUpdated

Drag to rotate · Right-drag to pan · Click, then scroll to zoom · Space play · ←→ step

What's happening

Pseudocode

    Try this in the 3D model

    • Run the first scenario. In which round does TCP leave slow start, and why there?
    • After the timeout in round 8, what is the new ssthresh? Check it against cwnd just before the loss.
    • Switch to Tahoe and run again. How do the bars after the 3 duplicate ACKs change?
    • Type your own losses, for example 6:dup, 12:dup, 18:timeout, and watch the sawtooth.

    Why congestion control?

    Sliding windows let the sender have several segments in flight at once, and flow control keeps the receiver from being flooded. But the network in between has limits too. If every sender pushes as fast as it can, router queues overflow, packets are dropped, senders retransmit, and the internet can grind to a halt. This actually happened in October 1986, when throughput between two nearby Berkeley sites fell from 32 kbit/s to 40 bit/s: a congestion collapse.

    Van Jacobson’s fix, now built into every TCP stack, adds a second window. The congestion window (cwnd) is how much data the sender allows itself in flight, based on what the network seems able to handle. The sender may send min(cwnd, receiver window) per round trip.

    The four rules

    TCP measures cwnd in MSS (maximum segment size) and changes it once per round trip (RTT):

    1. Slow start (cwnd < ssthresh): every ACK adds one MSS, so cwnd doubles every RTT: 1, 2, 4, 8, … Despite the name, the growth is exponential. It only starts small.
    2. Congestion avoidance (cwnd ≥ ssthresh): add just 1 MSS per RTT: 16, 17, 18, … carefully probing for spare capacity.
    3. Timeout (no ACK in time, so probably heavy congestion): ssthresh ← cwnd / 2 and cwnd ← 1. Back to slow start.
    4. Three duplicate ACKs (one segment lost, but later ones still arrive): ssthresh ← cwnd / 2, then
      • TCP Tahoe: cwnd ← 1, the same as a timeout;
      • TCP Reno: cwnd ← ssthresh (fast recovery), then continue linearly.

    ssthresh (the slow-start threshold) is TCP’s memory of “where trouble started last time”.

    Worked example

    ssthresh starts at 16. There is a timeout in round 8 and 3 duplicate ACKs in round 17, using TCP Reno:

    Round 1 2 3 4 5 6 7 8 9 10 11 12 13 … 17 18
    cwnd 1 2 4 8 16 17 18 19 1 2 4 8 9 … 13 6
    ssthresh 16 16 16 16 16 16 16 16 9 9 9 9 9 … 9 6
    • Rounds 1–4: slow start doubles the window, and in round 5 it reaches ssthresh = 16.
    • Rounds 5–8: congestion avoidance adds 1 per round.
    • Round 8: timeout at cwnd = 19, so ssthresh = 9 and cwnd = 1.
    • Rounds 9–13: slow start again, capped at the new ssthresh of 9, then linear.
    • Round 17: 3 duplicate ACKs at cwnd = 13, so ssthresh = 6. Reno continues from 6, while Tahoe would restart from 1.

    (In this model, doubling stops exactly at ssthresh. Real stacks count per ACK and may slightly overshoot.)

    The sawtooth and fairness

    Long-running TCP connections settle into a sawtooth: the window climbs linearly until a loss, halves, and climbs again. This AIMD behaviour (additive increase, multiplicative decrease) has a nice property: when several connections share a link, they converge towards equal shares of the bandwidth.

    Code

    def simulate(ssthresh, rounds, events, reno=True):
        cwnd, history = 1, []
        for r in range(1, rounds + 1):
            history.append(cwnd)
            event = events.get(r)
            if event:                                     # loss in this round
                ssthresh = max(2, cwnd // 2)
                cwnd = 1 if event == "timeout" or not reno else ssthresh
            elif cwnd < ssthresh:
                cwnd = min(2 * cwnd, ssthresh)            # slow start
            else:
                cwnd += 1                                 # congestion avoidance
        return history
    
    print(simulate(16, 18, {8: "timeout", 17: "dup"}))
    # [1, 2, 4, 8, 16, 17, 18, 19, 1, 2, 4, 8, 9, 10, 11, 12, 13, 6]

    Beyond Reno

    Variant Key idea Used by
    Tahoe (1988) Slow start, congestion avoidance, fast retransmit historical
    Reno (1990) + fast recovery after 3 duplicate ACKs textbooks, older systems
    NewReno Handles several losses in one window better many systems
    CUBIC Window grows as a cubic function of time since the last loss Linux, Windows, macOS default
    BBR Models bandwidth and RTT instead of reacting to loss Google, YouTube

    Common mistakes

    • Confusing the congestion window (protects the network) with the receiver window (protects the receiver). The sender uses the smaller of the two.
    • Thinking slow start is slow. It grows exponentially.
    • After a timeout, setting ssthresh to half of the old ssthresh instead of half of the current cwnd.
    • Treating 3 duplicate ACKs like a timeout in Reno. Only Tahoe drops cwnd to 1 there.

    Complexity at a glance

    Case / operationTimeWhy
    Slow startdoubles per RTTExponential growth until cwnd reaches ssthresh.
    Congestion avoidance+1 MSS per RTTAdditive increase.
    After a losscwnd / 2Multiplicative decrease (to 1 after a timeout).
    Rounds to reach a window of W from 1about log₂ WThanks to slow start.

    Quick check

    Test yourself — pick an answer to see if you got it.

    1. The congestion window is 8 MSS and ssthresh is 16. What is cwnd after the next RTT without loss?

    2. cwnd is 20 MSS when a timeout occurs. What are the new ssthresh and cwnd?

    3. Why does TCP Reno not reset cwnd to 1 after 3 duplicate ACKs?

    4. What does AIMD stand for?

    Saved only in this browser — no account needed.
    Spotted a mistake or a bug in the 3D model?

    Report a mistake

    in TCP Congestion Control. Thank you — every report makes the lesson better for the next reader.

    We'll also include a link to the step of the 3D model you're on and your browser type, so we can reproduce it.