Bandwidth Estimation — When 10% DAU Math Fails
A 200 Gbps pipe hit 1.6 Tbps demand when 50% DAU joined a live event.
20+ years shipping large-scale distributed systems. Notes here come from systems that actually shipped.
- ✓Solid grasp of fundamentals
- ✓Comfortable reading code examples
- ✓Basic production concepts
- Core concept: translate user behavior into Mbps through concurrent users and data per action
- Key inputs: DAU, concurrent fraction (10% typical), peak multiplier (3–5×), payload size
- Separate ingress (writes) and egress (reads) — ratio can reach 1000:1
- Add 20% protocol overhead for TCP/TLS/retransmissions
- Production insight: wrong payload size assumptions cause 10× estimate errors — always measure, don't guess
- Biggest mistake: conflating storage throughput (IOPS) with network throughput (Mbps)
Bandwidth estimation is the practice of calculating the peak data transfer rate your system must sustain, measured in bytes per second (or bits per second), based on user behavior, request patterns, and payload sizes. It's not a throughput guess—it's a deterministic calculation that accounts for concurrent users, request rates, average payload sizes, and protocol overhead.
The core formula is simple: bandwidth = (number of concurrent users) × (requests per second per user) × (average bytes per request), but the devil is in the details—like distinguishing read bandwidth from write bandwidth, which often differ by orders of magnitude in real systems (e.g., a video streaming service might have 100:1 read-to-write ratio).
This technique exists because naive DAU-based math (e.g., '10% of daily active users times average payload') fails catastrophically for bursty or real-time workloads. For example, a chat app with 10 million DAU might estimate 1 Gbps using DAU math, but peak concurrent usage during a global event could spike to 50 Gbps.
Bandwidth estimation forces you to model concurrency, not just daily totals, and to account for protocol overhead (TCP headers, TLS handshakes, HTTP/2 framing) that can add 10-30% to raw payload sizes. Tools like Cloudflare's bandwidth calculator or AWS's Simple Monthly Calculator embed these principles, but you still need to sanity-check against known reference points: a 4K Netflix stream is ~25 Mbps, a Zoom call is ~3 Mbps, and a single HTTP API response is typically 1-50 KB.
Where it fits: bandwidth estimation is a prerequisite for capacity planning, cloud cost forecasting, and network architecture decisions (e.g., choosing between a 1 Gbps vs 10 Gbps uplink). It's most critical for real-time systems (video conferencing, live streaming, multiplayer games) and high-throughput APIs (CDNs, data pipelines).
Don't use it when latency is your primary constraint—that's a different problem (tail latency, P99). And don't confuse it with throughput testing (which measures actual achieved rates under load); estimation is the predictive model, throughput testing validates it.
When your interview question asks 'how much bandwidth does this system need?', the answer isn't a number—it's the framework you use to derive it, including the hidden factors like protocol overhead that destroy naive calculations.
Imagine a highway. Bandwidth is how many lanes it has — more lanes means more cars (data) can travel at once. Estimating bandwidth is like asking: 'If a million people all drive to the stadium at the same time, how many lanes do I need so nobody sits in traffic?' You're not building the highway yet — you're figuring out how wide it needs to be before you pour a single drop of concrete.
Every time Netflix streams a 4K movie to your TV, someone at Netflix had to answer a deceptively simple question: how much network capacity do we actually need? Get it wrong in one direction and your service crawls to a halt during the Super Bowl. Get it wrong in the other and you're paying for server bandwidth that's sitting idle at 3 AM. Bandwidth estimation is the engineering discipline that keeps those two disasters apart — and it shows up in every serious system design conversation.
The problem it solves is the gap between 'I know my app needs to send data' and 'I know exactly how much network capacity to provision.' Without a structured estimation process, engineers either over-provision (wasting money) or under-provision (causing outages). Worse, when traffic spikes unexpectedly — a viral tweet, a product launch, a news event — an unprepared system collapses exactly when the world is watching. Bandwidth estimation gives you a defensible, back-of-the-envelope number you can act on.
By the end of this article you'll be able to walk into a system design interview and confidently estimate bandwidth requirements from first principles. You'll know how to translate user counts and behavior into bytes per second, how to account for peak traffic multipliers, and how to sanity-check your numbers so they hold up under scrutiny. You'll also understand the common traps — like forgetting bi-directional traffic or conflating storage throughput with network throughput — that trip up even experienced engineers.
Why Bandwidth Estimation Is Not a Throughput Guess
Bandwidth estimation is the set of techniques a client uses to measure the available capacity of a network path in real time, without requiring server cooperation. The core mechanic is a packet-pair probe: send two back-to-back packets of known size, measure the dispersion at the receiver, and derive the bottleneck link rate as size / inter-arrival gap. This gives a per-flow snapshot of the narrowest link, not the average throughput over a window.
In practice, estimation must handle cross-traffic noise, reverse-path asymmetry, and ACK compression. TCP-friendly rate control (TFRC) uses a loss-based model, while BBR relies on pacing gain cycles to sample the pipe. The key property is that a single probe can be misleading — you need multiple samples and a filter (e.g., a moving max or a Kalman filter) to converge on the true capacity. The error margin on a single packet pair can exceed 50% under bursty cross-traffic.
Use bandwidth estimation when you need to adapt video bitrate, tune congestion window, or detect path changes in real time. It matters because static provisioning fails: a 10% DAU math assumption about average bandwidth will collapse under tail latency or sudden cross-traffic spikes. Without estimation, you either underutilize the link or cause bufferbloat and retransmissions.
The Estimation Framework: From Users to Bytes Per Second
Every bandwidth estimate starts with the same three questions: How many users are active? What does each user do? How much data does each action move?
Think of it as a pipeline. You start with a user population, narrow it down to the concurrent slice that's actually active right now, then multiply by the per-user data rate. The result is your baseline bandwidth requirement.
The key formula is deceptively simple: Bandwidth = Concurrent Users × Average Data Rate per User. But the real skill is in correctly estimating each input. Most engineers underestimate concurrent users and underestimate per-action data size simultaneously, which means their final estimate can be off by 10x or more.
A practical starting point: assume roughly 10% of your daily active users (DAU) are online at any given moment during business hours. During peak events — a product launch or a viral moment — that multiplier can jump to 3x or 5x the average concurrent load. Always design for peak, not average. Average traffic is easy; peak traffic is what breaks systems.
The data-rate side needs equal care. A text message is ~1 KB. A compressed image thumbnail is ~50 KB. A 720p video stream is ~2 Mbps. A 4K stream is ~15-25 Mbps. Knowing these anchor values by heart lets you sanity-check any estimate in seconds.
# ───────────────────────────────────────────────────────────── # BandwidthEstimator.py # A structured bandwidth estimation calculator that mirrors # the thought process you'd use in a system design interview. # ───────────────────────────────────────────────────────────── def estimate_bandwidth( daily_active_users: int, concurrent_user_fraction: float, # e.g. 0.10 = 10% online at once peak_traffic_multiplier: float, # e.g. 3.0 = peak is 3× average avg_requests_per_user_per_second: float, # how often a user triggers a request avg_payload_size_bytes: int # average size of one request+response round trip ) -> dict: """ Returns a breakdown of bandwidth at average and peak load. All output is in Megabits per second (Mbps) — the standard unit used when talking to network engineers and cloud providers. """ # Step 1: How many users are active at the same moment? average_concurrent_users = daily_active_users * concurrent_user_fraction peak_concurrent_users = average_concurrent_users * peak_traffic_multiplier # Step 2: Total requests per second across all concurrent users average_requests_per_second = average_concurrent_users * avg_requests_per_user_per_second peak_requests_per_second = peak_concurrent_users * avg_requests_per_user_per_second # Step 3: Convert to bytes per second, then bits per second, then Megabits per second # 1 byte = 8 bits; 1 Megabit = 1,000,000 bits (use 1,000 not 1,024 for network bandwidth) bytes_to_megabits = 8 / 1_000_000 average_bandwidth_mbps = average_requests_per_second * avg_payload_size_bytes * bytes_to_megabits peak_bandwidth_mbps = peak_requests_per_second * avg_payload_size_bytes * bytes_to_megabits # Step 4: Add a 20% overhead buffer for protocol headers, retransmissions, TLS handshakes overhead_factor = 1.20 average_bandwidth_with_overhead_mbps = average_bandwidth_mbps * overhead_factor peak_bandwidth_with_overhead_mbps = peak_bandwidth_mbps * overhead_factor return { "average_concurrent_users": int(average_concurrent_users), "peak_concurrent_users": int(peak_concurrent_users), "average_requests_per_second": round(average_requests_per_second, 1), "peak_requests_per_second": round(peak_requests_per_second, 1), "average_bandwidth_mbps": round(average_bandwidth_mbps, 2), "peak_bandwidth_mbps": round(peak_bandwidth_mbps, 2), "peak_bandwidth_with_overhead_mbps": round(peak_bandwidth_with_overhead_mbps, 2), "recommended_provision_mbps": round(peak_bandwidth_with_overhead_mbps * 1.25, 2) # Provision 25% above peak-with-overhead so you're never at 100% utilisation } # ───────────────────────────────────────────────────────────── # EXAMPLE: Estimating bandwidth for a photo-sharing app # similar to early Instagram (upload + feed browsing) # ───────────────────────────────────────────────────────────── photo_app_estimate = estimate_bandwidth( daily_active_users = 5_000_000, # 5 million DAU concurrent_user_fraction = 0.10, # 10% active at once during the day peak_traffic_multiplier = 3.0, # 3× surge during evening peak hours avg_requests_per_user_per_second= 0.05, # one request every 20 seconds (browsing feed) avg_payload_size_bytes = 120_000 # ~120 KB per feed item (compressed image + metadata) ) print("══════════════════════════════════════════") print(" Bandwidth Estimation — Photo Sharing App") print("══════════════════════════════════════════") for metric, value in photo_app_estimate.items(): label = metric.replace("_", " ").title() unit = "Mbps" if "mbps" in metric else "users" if "users" in metric else "req/s" print(f" {label:<45} {value:>12} {unit}") print("══════════════════════════════════════════")
Read vs. Write Bandwidth: Why Direction Matters More Than You Think
Here's a mistake that trips up even experienced engineers: treating bandwidth as a single bidirectional number. In reality, most systems are heavily read-skewed — users consume far more data than they produce — and your bandwidth allocation needs to reflect that asymmetry.
Consider Twitter. For every tweet written (upload), it gets read by thousands of timelines (download). The write bandwidth might be measured in megabits per second while the read bandwidth is in gigabits. If you design a symmetric network connection, you've wildly over-provisioned writes and catastrophically under-provisioned reads.
The standard technique is to split your estimation into two independent calculations: ingress bandwidth (data flowing into your servers from users — uploads, API requests, form submissions) and egress bandwidth (data flowing out — page loads, media streams, API responses). For most consumer apps, the read-to-write ratio sits between 5:1 and 100:1. For video streaming, it can be 1000:1.
This matters practically because cloud providers like AWS charge differently for ingress and egress. Egress is typically billed; ingress is often free. Getting the ratio wrong means your cost model is wrong before you've written a line of code.
Also remember: CDN offloading. If 80% of your reads are cacheable static assets, a CDN absorbs that egress before it hits your origin servers. Your origin bandwidth requirement drops dramatically — but your CDN bandwidth requirement rises by the same amount, just at a lower cost per GB.
# ───────────────────────────────────────────────────────────── # ReadWriteBandwidthSplit.py # Models ingress (write) and egress (read) bandwidth separately. # Based on a social video platform — think TikTok-scale estimates. # ───────────────────────────────────────────────────────────── from dataclasses import dataclass @dataclass class UserAction: name: str requests_per_active_user_per_day: float avg_payload_bytes: int direction: str # "ingress" or "egress" def bandwidth_from_actions( daily_active_users: int, concurrent_fraction: float, peak_multiplier: float, actions: list ) -> None: """ Breaks down bandwidth per action type and summarises total ingress vs egress at peak load. """ seconds_per_day = 86_400 concurrent_users = daily_active_users * concurrent_fraction peak_concurrent_users = concurrent_users * peak_multiplier bytes_to_mbps = 8 / 1_000_000 print(f"\nDAU: {daily_active_users:,} | Avg Concurrent: {int(concurrent_users):,} | Peak Concurrent: {int(peak_concurrent_users):,}") print(f"{'─'*80}") print(f"{'Action':<30} {'Direction':<10} {'Req/s (peak)':>14} {'Bandwidth (Mbps)':>18}") print(f"{'─'*80}") total_ingress_mbps = 0.0 total_egress_mbps = 0.0 for action in actions: # Convert daily requests per user into requests per second requests_per_user_per_second = action.requests_per_active_user_per_day / seconds_per_day # Scale by peak concurrent users peak_requests_per_second = peak_concurrent_users * requests_per_user_per_second # Bandwidth in Megabits per second bandwidth_mbps = peak_requests_per_second * action.avg_payload_bytes * bytes_to_mbps print(f"{action.name:<30} {action.direction:<10} {peak_requests_per_second:>14,.0f} {bandwidth_mbps:>17,.1f}") if action.direction == "ingress": total_ingress_mbps += bandwidth_mbps else: total_egress_mbps += bandwidth_mbps print(f"{'─'*80}") print(f"{'TOTAL INGRESS (upload)':<30} {'':10} {'':>14} {total_ingress_mbps:>17,.1f} Mbps") print(f"{'TOTAL EGRESS (download)':<30} {'':10} {'':>14} {total_egress_mbps:>17,.1f} Mbps") read_write_ratio = total_egress_mbps / total_ingress_mbps if total_ingress_mbps > 0 else float('inf') print(f"\n Read-to-Write Ratio: {read_write_ratio:.0f}:1") print(f" Egress is {read_write_ratio:.0f}× higher — size your egress pipe accordingly.") # ───────────────────────────────────────────────────────────── # Social Video Platform — similar scale to early TikTok # ───────────────────────────────────────────────────────────── platform_actions = [ UserAction( name = "Video Upload (60s clip)", requests_per_active_user_per_day = 0.5, # half of users upload once a day avg_payload_bytes = 50_000_000, # 50 MB compressed video direction = "ingress" ), UserAction( name = "Video Stream (watch)", requests_per_active_user_per_day = 120, # 120 videos watched per day avg_payload_bytes = 5_000_000, # 5 MB per streamed video segment batch direction = "egress" ), UserAction( name = "Feed API Request", requests_per_active_user_per_day = 60, # refresh feed ~60 times per day avg_payload_bytes = 15_000, # 15 KB JSON metadata per feed load direction = "egress" ), UserAction( name = "Comment / Like Submit", requests_per_active_user_per_day = 30, avg_payload_bytes = 500, # tiny JSON payload direction = "ingress" ), ] bandwidth_from_actions( daily_active_users = 10_000_000, # 10M DAU concurrent_fraction = 0.08, # 8% concurrent peak_multiplier = 4.0, # 4× peak (viral video moment) actions = platform_actions )
Sanity-Checking Your Estimates with Known Reference Points
A bandwidth number in isolation is meaningless. 108,000 Mbps sounds enormous — but is it? You need reference anchors to know whether your estimate is in the right ballpark or wildly off.
Here's a mental model that works: map your estimate to physical infrastructure you understand. A standard 10 GbE (10 Gigabit Ethernet) server NIC moves 10,000 Mbps. A 100 GbE link moves 100,000 Mbps. If your estimate says you need 108,000 Mbps, that's roughly one 100 GbE uplink at full saturation — which is a single top-of-rack switch port. That's very achievable, but leaves zero headroom. You'd provision at least two to four such links with load balancing.
For internet-scale comparisons: Netflix at peak reportedly consumes around 15% of global internet bandwidth — that's measured in terabits per second. If your estimate for a 5M DAU app lands at a petabit per second, something is wrong with your inputs.
The other sanity check is per-user arithmetic. Divide your total bandwidth by your concurrent user count. If the per-user number is 500 Mbps, that's clearly wrong — no consumer ISP delivers that. If it's 0.0001 bps, you've missed a factor somewhere. For a typical app, per-user bandwidth at peak should land between 10 Kbps (light text/API app) and 25 Mbps (4K video). Anything outside that range deserves a second look at your assumptions.
# ───────────────────────────────────────────────────────────── # BandwidthSanityChecker.py # Validates a bandwidth estimate against known reference points # and flags results that are implausible. # ───────────────────────────────────────────────────────────── from enum import Enum class AppType(Enum): TEXT_API = ("Text / API heavy", 10_000, 5_000_000) # 10 Kbps – 5 Mbps per user IMAGE_FEED = ("Image feed / social", 500_000, 50_000_000) # 500 Kbps – 50 Mbps per user VIDEO_SD = ("SD Video Streaming", 1_000_000, 5_000_000) # 1 Mbps – 5 Mbps per user VIDEO_HD_4K = ("HD/4K Video Streaming", 5_000_000, 25_000_000) # 5 Mbps – 25 Mbps per user def __init__(self, label, min_bps_per_user, max_bps_per_user): self.label = label self.min_bps_per_user = min_bps_per_user self.max_bps_per_user = max_bps_per_user # Known infrastructure reference points (in Mbps) INFRASTRUCTURE_TIERS = [ ("1 GbE server NIC", 1_000), ("10 GbE server NIC", 10_000), ("25 GbE link", 25_000), ("100 GbE top-of-rack port", 100_000), ("400 GbE backbone link", 400_000), ("1 Tbps backbone", 1_000_000), ] def sanity_check( estimated_total_bandwidth_mbps: float, peak_concurrent_users: int, app_type: AppType ) -> None: """ Checks whether a bandwidth estimate makes sense by: 1. Calculating per-user bandwidth and checking against known ranges 2. Mapping total bandwidth to physical infrastructure units """ print("\n" + "═" * 60) print(" BANDWIDTH SANITY CHECK") print("═" * 60) print(f" App type: {app_type.label}") print(f" Total bandwidth: {estimated_total_bandwidth_mbps:,.1f} Mbps") print(f" Peak concurrent users: {peak_concurrent_users:,}") # ── Check 1: Per-user bandwidth ─────────────────────────── # Convert total Mbps to bits per second, divide per user total_bps = estimated_total_bandwidth_mbps * 1_000_000 bps_per_user = total_bps / peak_concurrent_users mbps_per_user = bps_per_user / 1_000_000 min_expected_bps = app_type.min_bps_per_user max_expected_bps = app_type.max_bps_per_user print(f"\n Per-user bandwidth: {mbps_per_user:.3f} Mbps") print(f" Expected range: {min_expected_bps/1_000_000:.3f} – {max_expected_bps/1_000_000:.1f} Mbps") if bps_per_user < min_expected_bps: print(" ⚠️ BELOW RANGE — Estimate may be too low. Check payload sizes.") elif bps_per_user > max_expected_bps: print(" ⚠️ ABOVE RANGE — Estimate may be too high. Check request frequency.") else: print(" ✅ Per-user bandwidth is within expected range.") # ── Check 2: Map to infrastructure ─────────────────────── print("\n Infrastructure mapping:") infrastructure_needed = None for infra_name, infra_capacity_mbps in INFRASTRUCTURE_TIERS: links_needed = estimated_total_bandwidth_mbps / infra_capacity_mbps if links_needed <= 10: # Flag the first tier that needs 10 or fewer links infrastructure_needed = (infra_name, links_needed) print(f" → Requires {links_needed:.1f}× {infra_name} links") break if infrastructure_needed is None: print(" → Exceeds 400 GbE — you need a multi-Tbps backbone. Double-check inputs.") print("═" * 60) # ── Example 1: Reasonable estimate for an image-heavy social app ── sanity_check( estimated_total_bandwidth_mbps = 108_000, peak_concurrent_users = 1_500_000, app_type = AppType.IMAGE_FEED ) # ── Example 2: A suspiciously high estimate — let's see it flagged ── sanity_check( estimated_total_bandwidth_mbps = 5_000_000, peak_concurrent_users = 10_000, app_type = AppType.TEXT_API )
Putting It All Together: A Real Interview Walkthrough
Let's simulate exactly what you'd do if an interviewer said: 'Design YouTube. How much bandwidth do you need?'
Step 1 — Clarify scope. Ask: read-heavy or write-heavy focus? What resolution? Global or regional? These aren't stalling tactics — they're how you nail down inputs.
Step 2 — Anchor on DAU. YouTube has ~80M daily active users globally. You won't memorise this, but you'll be told or you estimate from context clues.
Step 3 — Separate reads and writes. ~0.5% of users upload (the 1% rule of internet content creation). The rest watch. Uploads average ~500 MB per video. Views average ~300 MB per video (mix of resolutions). Average user watches 5 videos per day.
Step 4 — Calculate peak. 8M concurrent viewers (10% of DAU), 3× evening peak = 24M peak concurrent. At 300 MB per video × 5 MB/min average stream rate, that's roughly 2.5 Mbps per viewer × 24M viewers = 60 Tbps egress at peak.
Step 5 — State CDN impact. CDN caches ~90% of popular content. Origin servers handle only ~6 Tbps. That's a manageable fleet of 400 GbE uplinks.
Step 6 — Sanity check. YouTube at ~15% of global internet bandwidth. Global internet ~ 700 Tbps. 15% = ~105 Tbps. Our 60 Tbps total egress is in the right order of magnitude. The estimate holds.
# ───────────────────────────────────────────────────────────── # YouTubeInterviewEstimate.py # End-to-end bandwidth estimation for a YouTube-scale system. # Shows the complete thought process you'd walk through out loud # in a system design interview — no shortcuts. # ───────────────────────────────────────────────────────────── def youtube_scale_estimation(): # ── INPUTS: State assumptions explicitly ───────────────── daily_active_users = 80_000_000 # 80M DAU (known public figure) uploader_fraction = 0.005 # 0.5% of DAU upload content avg_upload_size_bytes = 500_000_000 # 500 MB per uploaded video (pre-transcoding) avg_videos_watched_per_day = 5 # each user watches 5 videos avg_stream_rate_mbps = 2.5 # mix of 360p, 720p, 1080p = avg ~2.5 Mbps concurrent_fraction = 0.10 # 10% of DAU active at once peak_multiplier = 3.0 # 3× surge in evening hours cdn_offload_fraction = 0.90 # CDN absorbs 90% of view traffic bytes_to_mbps = 8 / 1_000_000 seconds_per_day = 86_400 print("\n" + "═" * 65) print(" YOUTUBE-SCALE BANDWIDTH ESTIMATION — INTERVIEW WALKTHROUGH") print("═" * 65) # ── WRITE (INGRESS): Upload bandwidth ──────────────────── uploaders_per_day = daily_active_users * uploader_fraction upload_bytes_per_second = (uploaders_per_day * avg_upload_size_bytes) / seconds_per_day ingress_bandwidth_mbps = upload_bytes_per_second * bytes_to_mbps print(f"\n📤 INGRESS (Uploads)") print(f" Uploaders per day: {uploaders_per_day:>12,.0f}") print(f" Upload data rate: {ingress_bandwidth_mbps:>12,.1f} Mbps") # ── READ (EGRESS): Streaming bandwidth ─────────────────── # Average concurrent viewers across the day avg_concurrent_viewers = daily_active_users * concurrent_fraction # Peak concurrent viewers (evening surge) peak_concurrent_viewers = avg_concurrent_viewers * peak_multiplier # Total egress at peak: every concurrent viewer is streaming total_egress_peak_mbps = peak_concurrent_viewers * avg_stream_rate_mbps total_egress_peak_tbps = total_egress_peak_mbps / 1_000_000 # convert to Tbps print(f"\n📥 EGRESS (Streaming)") print(f" Avg concurrent viewers: {avg_concurrent_viewers:>12,.0f}") print(f" Peak concurrent viewers: {peak_concurrent_viewers:>12,.0f}") print(f" Total egress at peak: {total_egress_peak_mbps:>12,.0f} Mbps ({total_egress_peak_tbps:.1f} Tbps)") # ── CDN OFFLOADING ──────────────────────────────────────── # Popular videos are cached at edge nodes; origin only handles the rest origin_egress_mbps = total_egress_peak_mbps * (1 - cdn_offload_fraction) cdn_egress_mbps = total_egress_peak_mbps * cdn_offload_fraction print(f"\n🌐 CDN OFFLOADING (90% cache hit rate)") print(f" CDN handles: {cdn_egress_mbps:>12,.0f} Mbps") print(f" Origin servers handle: {origin_egress_mbps:>12,.0f} Mbps") # ── INFRASTRUCTURE SIZING ───────────────────────────────── link_capacity_mbps = 400_000 # 400 GbE backbone link origin_links_needed = origin_egress_mbps / link_capacity_mbps print(f"\n🖥️ ORIGIN INFRASTRUCTURE SIZING") print(f" Link capacity used: 400 GbE per link") print(f" Links needed (origin): {origin_links_needed:>12.1f} × 400 GbE links") print(f" (Plus redundancy: provision 2× = {origin_links_needed*2:.0f} links)") # ── SANITY CHECK ────────────────────────────────────────── # YouTube ≈ 15% of ~700 Tbps global internet traffic global_internet_tbps = 700 youtube_share_fraction = 0.15 expected_youtube_tbps = global_internet_tbps * youtube_share_fraction our_estimate_tbps = total_egress_peak_tbps error_factor = abs(our_estimate_tbps - expected_youtube_tbps) / expected_youtube_tbps print(f"\n✅ SANITY CHECK") print(f" Our estimate: {our_estimate_tbps:>12.1f} Tbps") print(f" Expected (~15% of 700T): {expected_youtube_tbps:>12.1f} Tbps") print(f" Deviation: {error_factor*100:>12.1f}% {'✅ Within 2× — reasonable!' if error_factor < 1.0 else '⚠️ Off by more than 2× — revisit assumptions'}") print("═" * 65) youtube_scale_estimation()
Protocol Overhead: The Hidden Factor That Destroys Your Bandwidth Calculation
Your raw payload estimate is optimistic. Real network throughput is lower due to protocol overhead — TCP headers, TLS handshakes, retransmissions, and IP framing. A 20% overhead buffer is standard, but for small payloads the overhead can be 100% or more.
Consider a 1 KB chat message. TCP/IP headers add ~40 bytes per packet. TLS adds another ~40 bytes. That's ~8% overhead for a 1 KB payload. But for a 64-byte IoT sensor reading, the same 80 bytes of headers double the data you must transmit. Ignoring this can leave your service performing well below expectations.
TLS handshakes are particularly expensive. Every new connection requires a 2-RTT handshake plus certificate exchange (~4 KB). If your service handles many short-lived connections (e.g., mobile apps), the handshake overhead can consume significant bandwidth. Keep-alive connections are critical.
Retransmissions due to packet loss add another multiplier. On a link with 1% packet loss, TCP can lose up to 90% of throughput on a single connection due to congestion window collapse. For bulk transfers, open multiple TCP connections or use a protocol like QUIC (HTTP/3) that handles loss better.
In practice, provision 20% overhead for general HTTP APIs, 30% for TLS-heavy workloads, and consider 100% overhead if your payloads average under 500 bytes. For video streaming, the overhead is smaller (~10%) because the frame payloads are large.
# ───────────────────────────────────────────────────────────── # ProtocolOverheadCalculator.py # Shows how protocol headers inflate bandwidth for different payload sizes. # Useful for adjusting your raw payload estimate before provisioning. # ───────────────────────────────────────────────────────────── def overhead_factor(payload_bytes: int, use_tls=True) -> float: """ Estimates the overhead multiplier for a single packet. Assumes one request per TCP connection. """ tcp_overhead = 40 # TCP + IP headers tls_overhead = 40 # TLS record overhead (approximate) total_header = tcp_overhead + (tls_overhead if use_tls else 0) return (payload_bytes + total_header) / payload_bytes def print_overhead_table(payloads: list): print(f"\n{'Payload Size':<20} {'No TLS':<15} {'With TLS':<15} {'Effective Overhead %':<25}") print("-" * 75) for p in payloads: no_tls = overhead_factor(p, use_tls=False) with_tls = overhead_factor(p, use_tls=True) eff_overhead = (with_tls - 1) * 100 print(f"{p:<20} {no_tls:<15.2f} {with_tls:<15.2f} {eff_overhead:<25.1f}%") print("Protocol Overhead Impact on Bandwidth") print_overhead_table([64, 256, 1024, 8192, 65536, 1_000_000]) # ── Example: 1 million requests per second, 1 KB payload, with TLS ── payload = 1024 # 1 KB req_sec = 1_000_000 overhead = overhead_factor(payload, use_tls=True) effective_bps = req_sec * payload * 8 * overhead # bits per second print(f"\nFor {req_sec:,} req/s with 1 KB payload and TLS:") print(f" Raw bitrate: {req_sec * payload * 8 / 1e6:.0f} Mbps") print(f" With overhead: {effective_bps / 1e6:.0f} Mbps (adds {round((overhead-1)*100)}% more)")
- Small payloads (under 1 KB) suffer 30%+ overhead – use binary protocols or batch requests
- Large payloads (over 64 KB) have negligible header overhead
- TLS handshakes add connection-level overhead – reuse connections via keep-alive
- Packet loss makes overhead worse – use multiple streams (HTTP/3 QUIC) or TCP tuning
Why Your Average Bandwidth Number Is a Lie
Most junior engineers compute bandwidth as total bytes divided by wall-clock time. That number looks clean. It's also useless for production design. Real traffic doesn't arrive averaged. It bursts. What matters isn't the mean—it's the tail. You need the 95th or 99th percentile of your bandwidth distribution, not the average. A system designed for average bandwidth will melt during a spike. The WHY: bursts saturate buffers, cause packet loss, and trigger TCP backoff. That backoff collapses throughput for all connections sharing the path. You don't estimate bandwidth to feed a calculator. You estimate it to size buffers, provision links, and set rate limiters. Until you think in percentiles, you're guessing. Plot your bandwidth measurements as a histogram. Look at the shape. If your tail is fat, your average is a trap.
// io.thecodeforge package main import ( "fmt" "sort" ) // BandwidthSample holds bytes transferred and duration for a single measurement. type BandwidthSample struct { Bytes int64 Duration float64 // seconds } // Bps converts a sample to bytes per second. func (s BandwidthSample) Bps() float64 { return float64(s.Bytes) / s.Duration } // ComputePercentile sorts samples, returns bps at given percentile (0-100). func ComputePercentile(samples []BandwidthSample, p float64) float64 { if len(samples) == 0 { return 0 } sort.Slice(samples, func(i, j int) bool { return samples[i].Bps() < samples[j].Bps() }) idx := int(float64(len(samples)-1) * p / 100.0) return samples[idx].Bps() } func main() { data := []BandwidthSample{ {Bytes: 10485760, Duration: 1.0}, // 10 MB in 1s {Bytes: 20971520, Duration: 0.5}, // 20 MB in 0.5s -> 40 MB/s spike {Bytes: 5242880, Duration: 10.0}, // 5 MB in 10s -> 0.5 MB/s quiet } fmt.Printf("95th percentile: %.2f MB/s\n", ComputePercentile(data, 95)/1e6) }
Concurrency Multiplier: The Math They Don't Teach in Networking 101
You estimated bandwidth for one user. Then you multiplied by the number of users. Wrong. Users don't queue. They overlap. The correct question: How many concurrent connections will saturate a link? A single 4K video stream needs about 25 Mbps. But 1,000 users with live streams means 25 Gbps, plus TCP overhead. Multiply by your concurrency factor—peak simultaneous streams, not total registered users. Concurrency is a multiplier, not a flat count. The WHY: TCP congestion control shares the pipe. Every new connection competes for bandwidth with existing ones. Add too many and they all back off, causing retransmits and jitter. Estimate your peak concurrency from historical logs. If you don't have logs, assume 10x your average concurrent users as a risk margin. Then add buffer for retransmission overhead (5-10%). That number is your real bandwidth requirement.
// io.thecodeforge import java.time.Instant; import java.util.*; /** * Estimates required bandwidth given peak concurrency and average stream bitrate. */ public class BandwidthEstimator { record Connection(long startEpoch, long endEpoch, int bitrateMbps) {} static double estimateRequiredMbps(List<Connection> conns) { // Find max concurrent bitrate using sweep line var events = new ArrayList<Map.Entry<Long, Integer>>(); for (var c : conns) { events.add(Map.entry(c.startEpoch, c.bitrateMbps)); events.add(Map.entry(c.endEpoch, -c.bitrateMbps)); } events.sort(Map.Entry.comparingByKey()); int current = 0, peak = 0; for (var e : events) { current += e.getValue(); peak = Math.max(peak, current); } return peak * 1.1; // +10% TCP overhead } public static void main(String[] args) { var now = Instant.now().getEpochSecond(); var sample = List.of( new Connection(now, now+60, 25), // 25 Mbps for 60s new Connection(now+10, now+20, 50) // spike: 50 Mbps overlap ); System.out.printf("Required bandwidth: %.0f Mbps%n", estimateRequiredMbps(sample)); } }
Underestimated Peak Bandwidth Causes Global Outage
- For predictable events, estimate peak at 50-70% of DAU, not 10-30%
- CDN cache hit rate drops dramatically for new/live content – pre-populate
- Always test bandwidth scaling with a simulated load test before the real event
SanityChecker.sanity_check(total_mbps, concurrent_users, app_type)Cross-reference with known reference points (1 Mbps voice, 5 Mbps HD video)peak_viewers = DAU * concurrent_fraction * peak_multiplieregress_mbps = peak_viewers * avg_stream_rate_mbpsmeasure_actual_payload_size using tcpdump or cloud monitoringoverhead_factor = (measured_total / raw_payload) - 1| Aspect | Average Load Estimation | Peak Load Estimation |
|---|---|---|
| Purpose | Capacity planning for steady-state operations | Sizing for worst-case traffic bursts |
| Concurrent user fraction | ~10% of DAU | 30–50% of DAU during viral/event spikes |
| When to use | Cost modelling, monthly billing forecasts | Infrastructure provisioning, autoscaling limits |
| Risk if you use it alone | System collapses during traffic spikes | Massively over-provisioned; wastes budget |
| Typical multiplier vs. average | 1× (baseline) | 3× to 10× above average |
| CDN relevance | Cache hit rates reduce origin load meaningfully | CDN is critical — origin cannot absorb peak alone |
| Real-world example | Netflix weekday 2 AM traffic | Netflix Super Bowl halftime stream |
| Design decision driven | Storage tier sizing, baseline server count | Load balancer capacity, autoscaling trigger thresholds |
| File | Command / Code | Purpose |
|---|---|---|
| BandwidthEstimator.py | def estimate_bandwidth( | The Estimation Framework |
| ReadWriteBandwidthSplit.py | from dataclasses import dataclass | Read vs. Write Bandwidth |
| BandwidthSanityChecker.py | from enum import Enum | Sanity-Checking Your Estimates with Known Reference Points |
| YouTubeInterviewEstimate.py | def youtube_scale_estimation(): | Putting It All Together |
| ProtocolOverheadCalculator.py | def overhead_factor(payload_bytes: int, use_tls=True) -> float: | Protocol Overhead |
| bandwidth_percentile.go | "fmt" | Why Your Average Bandwidth Number Is a Lie |
| BandwidthEstimator.java | /** | Concurrency Multiplier |
Key takeaways
Common mistakes to avoid
4 patternsMixing bytes and bits in calculations
Using DAU instead of concurrent active users for egress
Ignoring protocol overhead and retransmissions
Assuming symmetric bandwidth (ingress = egress) for read-heavy apps
Interview Questions on This Topic
How would you estimate the required bandwidth for a system like WhatsApp (text + media)?
Explain the difference between average and peak bandwidth estimation. Which should you use for provisioning?
Your team's bandwidth estimate is 50 Gbps for a video streaming app with 1M DAU. Is that plausible?
Frequently Asked Questions
Generally 10% of DAU during business hours. During peak events, it can reach 30-60% depending on the nature of the event. Always confirm with the interviewer.
Only if you know the compression ratio. For text/API responses, assume 3-5× compression. For already compressed images/video, do not double-compress. It's safer to estimate uncompressed raw payloads and mention compression as a possible optimization.
Estimate both directions separately. For chat, each message is sent and then delivered (server push or poll). The total bandwidth is roughly 2× the upload bandwidth (ingress + egress). Use the same formula: concurrent users × messages per user per second × payload size × overhead.
Absolutely. Use round numbers and justify them: 'I'll assume 10% concurrent users as a starting point.' Interviewers care about your logical framework, not precise accuracy. A well-reasoned estimate within 10× of reality is excellent.
20+ years shipping large-scale distributed systems. Notes here come from systems that actually shipped.
That's Estimation. Mark it forged?
7 min read · try the examples if you haven't