Loading content...
A surgeon performing microsurgery doesn't make sweeping gestures—each movement is measured in millimeters, precisely controlled, with full awareness of the consequences of even slight errors. The stakes demand patience over speed.
TCP's linear growth during congestion avoidance embodies this same philosophy. After the rapid capacity discovery of slow start, TCP shifts to incremental probing—adding exactly one segment per round-trip time. This seemingly slow approach is precisely what enables the Internet to remain stable while billions of connections compete for bandwidth.
This page provides a deep dive into linear growth: its mathematical foundations, its impact on throughput and recovery time, its role in network stability, and how it compares to alternative approaches. Understanding linear growth completes your knowledge of TCP's fundamental congestion control mechanisms.
This page covers the mathematical properties of linear growth, how it impacts throughput over time, recovery time analysis after congestion events, the relationship between linear growth and network stability, packet-level dynamics of linear increase, and why linear growth remains the foundation even in advanced TCP variants.
Let's formally analyze the mathematics of TCP's linear growth during congestion avoidance. Unlike slow start's exponential behavior, linear growth has predictable, tractable properties.
The fundamental equation:
During congestion avoidance, the congestion window follows:
cwnd(t) = cwnd₀ + t × MSS
where:
Comparison with exponential growth:
| Property | Linear Growth | Exponential Growth |
|---|---|---|
| Formula | cwnd(t) = cwnd₀ + t | cwnd(t) = cwnd₀ × 2^t |
| Derivative | d(cwnd)/dt = 1 MSS | d(cwnd)/dt = cwnd × ln(2) |
| Time to double | t = cwnd₀ | t = 1 |
| Time to reach W | t = W - cwnd₀ | t = log₂(W/cwnd₀) |
| Target Window | Linear Growth | Exponential Growth | Difference |
|---|---|---|---|
| 20 MSS | 10 RTTs | 1 RTT | 10× slower |
| 100 MSS | 90 RTTs | 3.3 RTTs | 27× slower |
| 1000 MSS | 990 RTTs | 6.6 RTTs | 150× slower |
| 10,000 MSS | 9,990 RTTs | 10 RTTs | 999× slower |
| 100,000 MSS | 99,990 RTTs | 13.3 RTTs | 7518× slower |
The key insight:
The asymptotic difference is profound:
For large windows, this difference spans orders of magnitude. A 10 Gbps link with 100ms RTT has a BDP of ~85,000 segments. Linear growth from 1 segment would take 85,000 RTTs (2.4 hours!) while exponential would take only 17 RTTs (1.7 seconds).
Why TCP accepts this trade-off:
The growth rate of 1 MSS per RTT is a design choice, not a physical constraint. TCP could have used 0.5 MSS/RTT (more conservative) or 2 MSS/RTT (more aggressive). The choice of 1 MSS/RTT represents a balance that works well across diverse network conditions and has been validated by decades of Internet operation.
A critical concern with linear growth is recovery time after a congestion event. Let's analyze how long it takes to return to full capacity after various scenarios:
Scenario: Recovery after cwnd halving
After loss, cwnd is typically halved. With linear growth:
Pre-loss cwnd: W (at capacity)
Post-loss cwnd: W/2 (halved)
Growth rate: 1 MSS per RTT
Recovery time: (W - W/2) = W/2 RTTs
In other words: recovery time equals half the window size in segments.
Concrete examples:
| Network Scenario | Capacity | BDP (segments) | Recovery RTTs | Wall-Clock Time |
|---|---|---|---|---|
| DSL (10 Mbps, 20ms) | 10 Mbps | ~14 | 7 RTTs | 0.14 seconds |
| Fast broadband (100 Mbps, 30ms) | 100 Mbps | ~215 | 107 RTTs | 3.2 seconds |
| Datacenter (10 Gbps, 0.5ms) | 10 Gbps | ~427 | 213 RTTs | 0.11 seconds |
| Cross-continental (1 Gbps, 80ms) | 1 Gbps | ~6850 | 3425 RTTs | 4.6 minutes |
| Satellite (10 Mbps, 600ms) | 10 Mbps | ~513 | 256 RTTs | 2.6 minutes |
| Transatlantic (10 Gbps, 100ms) | 10 Gbps | ~85,600 | 42,800 RTTs | 1.2 hours |
The LFN (Long Fat Network) problem:
The table reveals a striking issue: high-capacity, high-latency networks have unacceptably long recovery times. A 10 Gbps transatlantic link takes over an hour to recover from a single loss event!
This is the primary motivation for TCP variants like CUBIC and BBR:
CUBIC's solution:
BBR's solution:
The fundamental trade-off:
Faster recovery means more aggressive probing, which risks causing more losses. Linear growth's slow recovery is the price of its stability guarantee.
As network speeds increase (from 1 Gbps to 100 Gbps and beyond), linear growth becomes increasingly inadequate. A 100 Gbps network with 50ms RTT has a BDP of 625 MB (~428,000 segments). Recovery after a single loss: 428,000 RTTs = 5.9 hours! This is why high-speed networks increasingly use CUBIC, BBR, or specialized datacenter protocols like DCTCP.
Linear growth affects not just recovery time but also the average throughput a connection achieves. Let's analyze this relationship:
Average throughput during steady state:
In the classic sawtooth pattern, cwnd oscillates between W/2 and W (where W is the capacity-limited window). The average window is:
avg_cwnd = (W + W/2) / 2 = 3W/4
Average throughput:
avg_throughput = (3W/4) × MSS / RTT
This is 75% of the theoretical maximum—a 25% "tax" on efficiency due to the probing behavior.
Derivation of the 75% efficiency:
The cwnd follows a sawtooth:
The data transferred in one cycle:
Data = ∫(W/2 to W) cwnd d(RTT) = area under the sawtooth
= (W + W/2)/2 × (W/2) = 3W²/8 segment-RTTs
time per cycle = W/2 RTTs
Average cwnd = (3W²/8) / (W/2) = 3W/4
| Metric | Formula | Example (100 Mbps, 50ms RTT) | Implication |
|---|---|---|---|
| Capacity BDP | Bandwidth × RTT | 625 KB (428 segments) | Maximum useful cwnd |
| Average cwnd | 3W/4 | 321 segments (469 KB) | Steady-state window |
| Average throughput | 3/4 × Capacity | 75 Mbps | 25% below capacity |
| Cycle duration | W/2 RTTs | 214 RTTs (10.7 seconds) | Time between losses |
| Loss rate | 1 per cycle | 1 per 10.7 seconds | ~93 losses/hour |
The TCP throughput equation:
A famous result from network analysis gives TCP throughput as a function of loss rate:
Throughput ≈ (MSS × C) / (RTT × √p)
where:
Key insights from this equation:
Example calculation:
Link: 100 Mbps, 50ms RTT, 0.1% loss rate
Throughput = (1460 × 1.2) / (0.05 × √0.001)
= 1752 / (0.05 × 0.0316)
= 1752 / 0.00158
= 1.1 Mbps (only 1.1% of capacity!)
This demonstrates how sensitive TCP is to loss. Even 0.1% loss reduces a 100 Mbps link to barely 1 Mbps!
To maximize TCP throughput with linear growth: (1) Minimize RTT by placing servers closer to users, (2) Eliminate non-congestion losses (hardware issues, WiFi interference), (3) Use window scaling to allow large cwnd on high-BDP paths, (4) Ensure buffers are sized appropriately (BDP worth), (5) Consider CUBIC or BBR for high-BDP networks.
Let's examine how linear growth manifests at the packet level, which is essential for understanding and debugging network behavior:
Per-ACK behavior:
During congestion avoidance, the cwnd update rule is:
cwnd = cwnd + MSS × MSS / cwnd
This adds a fractional MSS per ACK. For cwnd = 50 segments:
Increment = 1460 × 1460 / (50 × 1460)
= 1460 / 50
= 29.2 bytes per ACK
After 50 ACKs:
Total increment = 50 × 29.2 = 1460 bytes = 1 MSS
Practical implementation:
Some implementations avoid fractional bytes using a counter:
// Linux-style congestion avoidance
if (tp->snd_cwnd_cnt >= tp->snd_cwnd) {
tp->snd_cwnd++; // Integer increment
tp->snd_cwnd_cnt = 0;
} else {
tp->snd_cwnd_cnt++;
}
This counts ACKs and increments cwnd by 1 MSS when enough ACKs accumulate.
1234567891011121314151617181920
# Packet trace during congestion avoidance# cwnd = 50 segments, MSS = 1460 bytes Time(ms) Event cwnd (bytes) Increment Notes────────────────────────────────────────────────────────────────0.000 ACK received 73,000 +29.2 1st ACK of RTT0.001 ACK received 73,029 +29.2 0.002 ACK received 73,058 +29.2 ... ... ... ... 0.049 ACK received 74,431 +29.2 49th ACK0.050 ACK received 74,460 +29.2 50th ACK (cwnd now 51) # Next RTT50.000 TX segment 1 74,460 Send 51 segments50.002 TX segment 2 ...50.100 TX segment 51 All segments sent100.000 ACK received 74,460 +28.6 51 ACKs needed now # After 51 RTTs, cwnd has grown from 50 to 101 segments (doubled)ACK clock effect:
The arrival rate of ACKs "clocks" the transmission of new segments. This creates a natural pacing:
Sender → Network → Receiver → ACKs back to Sender
↓ bottleneck spaces packets
↓ ACKs arrive spaced out
If the bottleneck link serializes packets with gaps, ACKs will arrive with corresponding gaps. The sender transmits new segments as each ACK arrives, naturally pacing at the bottleneck rate.
Burst behavior in linear growth:
Unlike slow start (which creates growing bursts), congestion avoidance tends toward steady transmission:
Slow start RTT n: |segment burst of 2ⁿ|............wait............|
Congestion avoid: |steady stream: segment-gap-segment-gap-segment-gap|
This smoother pattern reduces queue oscillation and improves coexistence with other flows.
To see linear growth: 1) Start a long file transfer, 2) Wait for slow start to complete (cwnd reaches ssthresh), 3) Use Statistics → TCP Stream Graph → Window Scaling, 4) Look for a steady, linear rise in cwnd. The slope should be approximately MSS/RTT bytes per second in window growth.
The choice of linear growth during congestion avoidance isn't just about individual connections—it's crucial for network-wide stability. Let's analyze why:
The stability requirement:
A stable network system must have a bounded response to perturbations. When N flows share a bottleneck:
Total traffic at time t = Σᵢ cwndᵢ(t)
For stability, perturbations (flows starting/ending, route changes) should decay rather than amplify.
Why exponential growth threatens stability:
With exponential growth during steady state:
Why linear growth ensures stability:
With linear growth:
| Growth Type | Per-Flow Growth Rate | Aggregate Growth/RTT | Time to Double Aggregate |
|---|---|---|---|
| Exponential (×2) | 2× per RTT | 100×2 next RTT | 1 RTT |
| Exponential (×1.5) | 1.5× per RTT | 100×1.5 next RTT | ~1.7 RTTs |
| Linear (+1) | +1 MSS per RTT | +100 MSS per RTT | ~avg_cwnd RTTs |
| Linear (+0.5) | +0.5 MSS per RTT | +50 MSS per RTT | ~2×avg_cwnd RTTs |
Global synchronization prevention:
Linear growth helps prevent global synchronization, where many flows simultaneously experience loss and recover in lockstep:
Synchronized (bad):
Time 1: All flows hit capacity, all drop
Time 2: All flows halve
Time 3: All flows grow linearly
Time 4: All flows hit capacity together again
→ Periodic surges and crashes
Desynchronized (good):
Flows enter/exit at different times
Linear growth means flows grow at same rate but start from different points
Losses spread over time as flows hit capacity individually
→ Smooth aggregate traffic
Random Early Detection (RED) reinforces this:
RED routers probabilistically drop packets before the queue fills. This desynchronizes flows by causing losses at different times for different flows. Combined with linear growth's gradual probing, RED creates smooth aggregate behavior.
Controlled system analysis:
From a control theory perspective, TCP congestion avoidance is a distributed feedback control system:
Linear growth (additive increase) provides a bounded slew rate, preventing oscillations that faster controllers might cause.
The 1986 congestion collapse occurred precisely because TCP lacked congestion avoidance. Connections retransmitted aggressively with no rate limiting. Van Jacobson's slow start and congestion avoidance—with its linear growth—solved this by bounding the aggregate traffic growth. The Internet's stability depends on this bound being respected.
While linear growth is the foundation, several alternatives have been developed to address its limitations on high-BDP networks:
CUBIC's Cubic Growth:
CUBIC (Linux default since 2006) uses a cubic function:
W(t) = C × (t - K)³ + Wmax
where K = ∛(Wmax × β / C)
C = CUBIC constant (0.4 by default)
Wmax = window size before last loss
This creates three growth regions:
High-Speed TCP (RFC 3649):
HSTCP adjusts the growth rate based on current cwnd:
Small cwnd: Normal linear growth (1 MSS/RTT)
Large cwnd: Faster growth (up to 1000× linear)
This addresses the recovery time issue on high-BDP networks while maintaining compatibility with standard TCP flows.
| Variant | Growth Function | Recovery from 50% Loss | Key Property |
|---|---|---|---|
| Reno | Linear (+1/RTT) | O(W) RTTs | Simple, fair, stable |
| CUBIC | Cubic polynomial | O(∛W) RTTs | Fast far from peak, slow near peak |
| High-Speed | Variable (cwnd-based) | O(√W) RTTs | Faster for large windows |
| Scalable | Multiplicative | O(log W) RTTs | Very fast, fairness concerns |
| Vegas | RTT-based | No loss-based drops | Proactive, sensitive to RTT |
| BBR | Model-based | Immediate (pacing rate) | No traditional recovery |
BBR's Departure from AIMD:
BBR (Bottleneck Bandwidth and RTT) fundamentally abandons loss-based congestion control:
Classic TCP: cwnd = f(loss events over time)
BBR: pacing_rate = estimated_bandwidth
cwnd = BDP + extra_for_variations
BBR doesn't "grow" linearly or otherwise—it estimates the path's characteristics and paces at the estimated rate. Recovery after loss is simply recalculating the estimate.
Why linear remains relevant:
Despite advanced alternatives:
For modern deployments: Use CUBIC (Linux default) for most scenarios—it's well-tested and handles high-BDP well. Consider BBR for networks where loss isn't primarily caused by congestion (wireless, satellite). Stick with Reno for compatibility testing or environments requiring predictable, conservative behavior. For data centers, consider DCTCP with ECN.
Understanding linear growth helps diagnose real-world TCP performance issues. Here's how to apply this knowledge:
Recognizing linear growth in practice:
Use Linux's ss command to observe cwnd progression:
# Watch cwnd changes over time
watch -n 0.1 'ss -ti dst example.com'
# Output includes:
# cubic wscale:8,7 rto:220 rtt:109.375/54.688 ...
# ato:40 mss:1460 pmtu:1500 rcvmss:1460 ...
# cwnd:42 ssthresh:36 bytes_sent:1234567 ...
During congestion avoidance:
Common issues related to linear growth:
Issue 1: Throughput stuck at low value
Symptom: Connection never reaches expected throughput.
Diagnosis:
# Check cwnd vs ssthresh
ss -ti | grep -E 'cwnd|ssthresh'
# If cwnd << ssthresh: Still in slow start, or...
# If cwnd ≈ ssthresh: ssthresh is too low (from past loss)
# If cwnd limited by rwnd: Receiver is bottleneck
Fix: Clear cached metrics, increase receiver buffers, or debug past loss events.
Issue 2: Very long recovery times
Symptom: After brief congestion, throughput stays low for minutes.
Diagnosis: Calculate expected recovery time = (old_cwnd - new_cwnd) × RTT
Fix: Switch to CUBIC or BBR for high-BDP networks.
Issue 3: Sawtooth amplitude too large
Symptom: Throughput oscillates wildly between high and very low.
Diagnosis: May indicate buffer bloat (large buffers delay congestion signal) or synchronized losses.
Fix: Enable ECN, reduce buffer sizes, or deploy AQM (RED, CoDel).
If your system uses CUBIC (likely on Linux), you won't see pure linear growth. CUBIC includes a linear 'TCP-friendliness' component but also has cubic terms. The ss output will still show cwnd changes; expect slightly faster recovery than pure linear when cwnd is well below the previous maximum.
We've completed our deep exploration of linear growth during TCP congestion avoidance. Let's consolidate the key insights:
Module Complete!
You have now mastered the fundamentals of TCP slow start and congestion avoidance—the algorithms that prevent the Internet from collapsing under its own traffic. You understand:
This knowledge forms the foundation for understanding advanced TCP variants, diagnosing network performance issues, and designing systems that work efficiently at scale.
Congratulations! You now understand TCP's slow start and congestion avoidance mechanisms at a deep level. These algorithms—elegant in their simplicity yet profound in their impact—enable billions of devices to share the Internet's capacity fairly and efficiently. From Van Jacobson's original 1988 design to modern variants like CUBIC and BBR, the principles of exponential probing, threshold-based transitions, and linear steady-state growth remain at the heart of TCP congestion control.