Slow start
A brand-new TCP connection has a problem neither endpoint can solve by just being fast: nobody knows yet how much the network path between them can actually carry. Guess too high and the first burst of data overwhelms a router buffer somewhere in the middle, causing exactly the loss Congestion control is trying to avoid. Guess too low and the connection wastes capacity that was sitting there the whole time. Slow start is TCP's answer to that uncertainty: begin cautiously, but ramp up fast enough that the caution doesn't cost much once the path turns out to be healthy.
Starting small on purpose
At the moment a connection reaches ESTABLISHED, its sender has no data at all about the path's real capacity — no history of round-trip times under load, no sense of where a bottleneck router's buffer might sit. RFC 5681 has the sender begin with a congestion window of just a few segments' worth of data (historically one, with modern implementations typically starting at ten, per RFC 6928's update). That's a deliberately conservative opening bet, made because sending too aggressively into an unknown path risks the one outcome TCP most wants to avoid on a fresh connection: an immediate congestion event before there's even a baseline round-trip-time measurement to react against.
Why "slow" start is actually the fast-growing phase
The name is genuinely counterintuitive, and it's worth addressing directly rather than letting it cause confusion later: slow start is the fastest-growing phase of TCP's entire congestion behavior, not the slowest. What's "slow" is only the starting point — one or a few segments — not the rate of growth from there.
The rule is simple: for every ACK received, increase the congestion window by one maximum segment size. Because every acknowledged segment triggers growth, and each round trip typically carries multiple segments, the congestion window doesn't just grow — it roughly doubles every round trip:
Round trip 1: send 1 segment → 1 ACK → CWND grows by 1 → CWND = 2
Round trip 2: send 2 segments → 2 ACKs → CWND grows by 2 → CWND = 4
Round trip 3: send 4 segments → 4 ACKs → CWND grows by 4 → CWND = 8
Round trip 4: send 8 segments → 8 ACKs → CWND grows by 8 → CWND = 16
That's exponential growth — the congestion window doubles each round trip, not merely increasing by a fixed amount. Compare that to TCP Reno's congestion-avoidance phase, which increases the window by roughly one segment per entire round trip, regardless of how many segments or ACKs that round trip contained — linear growth, dramatically more conservative. Slow start's real job is to get a connection from "unknown capacity" to "roughly the right neighborhood" as quickly as it safely can; congestion avoidance's job is to creep the rest of the way once that neighborhood has been found, testing the remaining headroom cautiously rather than doubling into it.
The threshold that ends slow start
Slow start doesn't run forever — it hands off to congestion avoidance once the congestion window reaches the slow start threshold (ssthresh), a value the connection maintains and adjusts based on what it's learned from past congestion events. A new connection typically starts with a high, permissive threshold (or none at all, in some implementations, until the first loss event sets one), so slow start initially has room to grow substantially before switching to the more conservative phase.
The threshold isn't static. Every time the connection detects loss (see Congestion control for the two ways this is detected), ssthresh is recalculated — generally based on roughly half the amount of data that was in flight at the moment loss was detected — and the congestion window drops. What happens next depends on how severe the loss signal was:
- After a full retransmission timeout — the harsher signal — the congestion window collapses all the way back to its original starting value, and the connection re-enters slow start from near zero, climbing back up toward the newly lowered
ssthresh. - After fast retransmit from duplicate ACKs — the milder signal — TCP Reno instead drops the congestion window only to the neighborhood of the new
ssthreshand enters congestion avoidance directly, skipping a full restart of slow start. This distinction — whether a lowered threshold is approached again through slow start or landed on directly — is one of the concrete differences the next article covers in detail.
Either way, once the congestion window reaches ssthresh, exponential growth stops and linear congestion avoidance takes over — the threshold is precisely the boundary between the two phases, recalculated downward every time the connection learns it pushed too far.
Reading the transition in a capture
The clearest visible signature of slow start in progress is the doubling pattern itself, seen through how many segments the sender pushes before pausing for acknowledgments:
16:20:01.001100 IP 10.0.5.12.52210 > 93.184.216.34.443: Flags [P.], seq 1:1401, ack 1, win 2053, length 1400
16:20:01.041550 IP 93.184.216.34.443 > 10.0.5.12.52210: Flags [.], ack 1401, win 1026, length 0
16:20:01.042010 IP 10.0.5.12.52210 > 93.184.216.34.443: Flags [P.], seq 1401:2801, ack 1, win 2053, length 1400
16:20:01.042390 IP 10.0.5.12.52210 > 93.184.216.34.443: Flags [P.], seq 2801:4201, ack 1, win 2053, length 1400
16:20:01.081920 IP 93.184.216.34.443 > 10.0.5.12.52210: Flags [.], ack 4201, win 1026, length 0
16:20:01.082310 IP 10.0.5.12.52210 > 93.184.216.34.443: Flags [P.], seq 4201:5601, ack 1, win 2053, length 1400
16:20:01.082680 IP 10.0.5.12.52210 > 93.184.216.34.443: Flags [P.], seq 5601:7001, ack 1, win 2053, length 1400
16:20:01.083050 IP 10.0.5.12.52210 > 93.184.216.34.443: Flags [P.], seq 7001:8401, ack 1, win 2053, length 1400
16:20:01.084420 IP 10.0.5.12.52210 > 93.184.216.34.443: Flags [P.], seq 8401:9801, ack 1, win 2053, length 1400
One segment in the first burst, two in the next, four in the one after that — the classic slow-start doubling, entirely consistent with the exponential growth described above, and visible directly in how many segments the sender is willing to push per round trip without waiting on more acknowledgments first.
Practical scenario: a large file transfer that's slow to reach full speed
A backup job uploading a multi-gigabyte file to object storage over a connection with a 150ms round-trip time consistently starts slow — the first second or two transfers well below the link's advertised capacity — before ramping up to near-line-rate and staying there for the rest of the transfer. The behavior is consistent and repeatable, not a sign of anything broken; it just costs a predictable, measurable amount of time on every fresh connection.
Given a 150ms round trip and an initial window of ten segments (roughly 14,600 bytes, per RFC 6928's default), reaching a congestion window large enough to fill even a modest 100 Mbps link takes several doublings, each one costing a full round trip:
RTT 1: CWND ≈ 10 segments (~14.6 KB)
RTT 2: CWND ≈ 20 segments (~29 KB)
RTT 3: CWND ≈ 40 segments (~58 KB)
RTT 4: CWND ≈ 80 segments (~117 KB)
RTT 5: CWND ≈ 160 segments (~234 KB)
At 150ms per round trip, reaching a window wide enough to sustain 100 Mbps takes on the order of a second of pure ramp-up — not a fault in the transfer, but the direct, arithmetic cost of starting cautiously on a path the connection has never measured before. This is precisely why very short-lived connections (a handful of small API requests, for instance) rarely benefit much from a large theoretical bandwidth — they finish before slow start ever gets close to using it — while a single long-lived transfer, like this backup job, eventually amortizes the ramp-up cost across gigabytes of data and reaches near-full throughput regardless. It's also the concrete numeric reason, beyond the general argument in Introduction to TCP, that a warm, already-established connection consistently outperforms a fresh one for anything beyond a trivially small request.
Practice exercises
- Using the doubling table from the file-transfer scenario, calculate roughly how many round trips it would take to reach a congestion window capable of sustaining 1 Gbps, starting from the same initial window of ten segments — and explain what that implies about slow start's relative cost on a very high-bandwidth, high-latency path (a satellite link, for instance) versus a low-latency data-center connection.
- A monitoring dashboard shows a service's outbound connections to a downstream API consistently taking longer to reach peak throughput after a recent change that shortened connection lifetimes (closing and reopening connections more frequently instead of reusing them). Explain the mechanism connecting that change to the observed slowdown.
Reaching the slow-start threshold answers "how do we get to roughly the right speed quickly," but not "what happens after that, or what happens when we guessed wrong." TCP Reno picks up exactly there — the original congestion-avoidance algorithm, and the baseline every later congestion-control algorithm in this module is measured against.