Networking Basics
Grasp the network fundamentals that underpin all distributed system architectures.
Why Networking Knowledge Makes or Breaks System Design Interviews
Imagine you're sitting across from a senior engineer at Google, Meta, or Stripe. You've practiced your algorithms, you've reviewed data structures, and you feel ready. Then they ask: "Design a URL shortener that handles 100 million requests per day." You start sketching databases and APIs — but then they probe deeper. "How does a client actually reach your service? What happens if a data center goes down? How do you reduce latency for users in Tokyo when your servers are in Virginia?" Suddenly, the conversation has moved from high-level boxes and arrows into the territory where systems actually live: the network. Grab our free flashcards at the end of each section to drill these concepts until they're second nature — because networking knowledge isn't just a nice-to-have in system design interviews. It's the foundation everything else is built on.
Most developers spend their careers working one or two layers above the network. Frameworks handle HTTP, cloud providers abstract away routing, and ORMs shield you from the database wire protocol. This abstraction is powerful — but it creates a dangerous blind spot. When an interviewer at a top tech company asks you to design a distributed system, they are specifically testing whether you can reason below those abstractions. They want to know: does this candidate understand why we use TCP for financial transactions but UDP for video streaming? Do they know how DNS resolution affects the perceived speed of their system? Can they explain what actually happens in the 200 milliseconds between a user pressing Enter and a webpage appearing on screen?
This lesson is the answer to those questions. By the time you finish, networking will stop feeling like magic and start feeling like a set of well-understood tools — each with tradeoffs you can speak to confidently in any interview room.
Why Interviewers Probe Networking Knowledge
System design interviews are fundamentally about one thing: demonstrating that you can build systems that work in the real world, at scale, under adversarial conditions. The real world runs on networks. Every architectural decision you make — where to place a cache, whether to use a message queue, how to structure your microservices, when to use a CDN — is ultimately constrained and shaped by the physics and protocols of networked communication.
Interviewers at companies like Amazon, Netflix, Uber, and Cloudflare ask about networking because they've all been burned by engineers who didn't understand it. They've seen outages caused by TCP connection exhaustion. They've watched latency budgets blow up because nobody accounted for DNS lookup time. They've had services cascade-fail because an engineer didn't understand how socket timeouts interact with retry logic. These aren't theoretical concerns — they're Monday morning incidents.
🎯 Key Principle: Networking knowledge separates engineers who can describe a system from engineers who can reason about whether it will actually work.
When you demonstrate that you understand, say, why HTTP/2 multiplexing matters for a mobile API, or why you'd choose UDP over TCP for a real-time game server, you signal to your interviewer that you think about systems the way production engineers do — not just the happy path, but the edge cases, the performance characteristics, and the failure modes.
The Direct Link Between Networking and Scalable, Reliable Systems
Here's a truth that experienced architects know but junior engineers often discover the hard way: most scalability problems are, at their core, networking problems. When a system struggles under load, the bottleneck is usually one of a small number of culprits — and almost all of them live in the network layer:
🧠 Too many open connections — your server runs out of file descriptors trying to maintain thousands of simultaneous TCP connections 📚 Chatty protocols — your microservices make dozens of synchronous network calls per user request, and latency compounds 🔧 DNS misconfiguration — clients cache stale records, sending traffic to a dead server for minutes after failover 🎯 Bandwidth saturation — your service transfers uncompressed JSON payloads when Protocol Buffers would use one-tenth the bytes 🔒 No circuit breakers — a slow downstream service causes your socket pool to fill up, taking down your entire application
Each of these failure modes maps directly to a networking concept you can learn, understand, and design around. This is why the networking section of a system design curriculum isn't a detour — it's the bedrock.
💡 Real-World Example: In 2021, Facebook experienced a roughly six-hour outage caused by a BGP (Border Gateway Protocol) misconfiguration that withdrew the routes to Facebook's DNS nameservers from the global internet. Even Facebook's own engineers couldn't reach the internal tools to fix it, because those tools also depended on the network that had just been severed. Understanding BGP and DNS isn't just academic — it's the difference between a recoverable incident and a $100M outage.
A Mental Map: Networking Layers and Where This Lesson Fits
Before we dive deep, let's orient ourselves. Networks are organized in layers — a concept you'll explore thoroughly in the next section on the OSI model and TCP/IP stack. For now, think of layers as a stack of translators: each one handles a specific job and hands work up or down to its neighbors.
┌─────────────────────────────────────────────┐
│ APPLICATION LAYER (HTTP, DNS, etc.) │ ← You work here daily
├─────────────────────────────────────────────┤
│ TRANSPORT LAYER (TCP, UDP) │ ← Reliability decisions
├─────────────────────────────────────────────┤
│ NETWORK LAYER (IP, Routing) │ ← Addressing & paths
├─────────────────────────────────────────────┤
│ DATA LINK + PHYSICAL (Ethernet, WiFi) │ ← Hardware & signals
└─────────────────────────────────────────────┘
▲ Data flows up to reach your app
▼ Data flows down to leave your machine
This lesson covers the fundamentals — the vocabulary, mental models, and reasoning skills that make every other networking topic make sense. The lessons that follow will build on this foundation:
- Section 2 dives into the OSI and TCP/IP models in detail
- Section 3 covers IP addresses, ports, and sockets — the primitives every distributed system uses
- Section 4 introduces latency, bandwidth, and throughput — the numbers that drive architecture decisions
- Section 5 covers the networking pitfalls most likely to trip you up in interviews and production
Think of this section as the "why" before all the "what" and "how" that follows.
🧠 Mnemonic: Think of network layers like shipping a package. You (application layer) write a letter and seal it. The post office (transport layer) puts it in a labeled envelope with a tracking number. The logistics network (network layer) routes it through sorting facilities. The delivery truck (physical layer) actually moves the atoms. Each layer doesn't need to know the details of the others — it just needs to do its own job and hand off correctly.
The Journey of a Single Click: What Really Happens
Nothing makes networking concepts click faster than tracing a real event from start to finish. Let's walk through what happens — at the network level — when a user opens https://www.example.com in their browser. This single, familiar action involves nearly every networking concept in this lesson.
Step 1: DNS Resolution
Before your browser can send a single byte to example.com, it needs to know where that server lives — specifically, its IP address. Domain names like example.com are human-readable aliases; the network only understands numbers. So your browser first performs a DNS (Domain Name System) lookup:
User types: https://www.example.com
Browser → OS DNS cache: "Do you know example.com?"
└─ Cache miss → Browser queries local DNS resolver
└─ Resolver queries Root DNS server → TLD server (.com)
└─ TLD server → Authoritative DNS for example.com
└─ Returns: "93.184.216.34"
Time elapsed: 20-120ms (often cached to <1ms)
This lookup adds measurable latency to every cold request. In system design, this is why DNS caching, TTL (Time to Live) tuning, and GeoDNS (routing users to the nearest data center) are important architectural choices — not implementation details.
Step 2: TCP Handshake
Now your browser knows the IP address. But before it can send an HTTP request, it needs to establish a reliable channel. TCP (Transmission Control Protocol) requires a three-way handshake before any data flows:
Client Server
│─── SYN ──────────────────────►│ "I want to connect"
│◄── SYN-ACK ───────────────────│ "OK, I'm ready"
│─── ACK ──────────────────────►│ "Great, let's go"
│ │
│─── HTTP GET / ───────────────►│ Now data can flow
This handshake adds one full round-trip time (RTT) of latency before a single byte of your web page is sent. For a server 100ms away, that's 100ms of pure overhead before the response starts. This is exactly why HTTP/2 and HTTP/3 (QUIC) were designed — they reduce or eliminate this overhead for repeated connections.
Step 3: TLS Negotiation (for HTTPS)
For a secure connection, there's an additional TLS (Transport Layer Security) handshake after the TCP handshake. Depending on the TLS version, this can add another 1-2 RTTs. TLS 1.3 reduced this to a single RTT, and with 0-RTT resumption, repeat visitors can skip it entirely.
Step 4: HTTP Request and Response
Finally, data flows. The browser sends an HTTP GET request; the server responds with HTML. But the HTML references CSS, JavaScript, and images — so this process repeats, often dozens of times. This is where connection pooling, HTTP/2 multiplexing, and CDNs (Content Delivery Networks) pay enormous dividends.
Step 5: Rendering
The browser parses, renders, and displays the page. From the user's perspective, this whole journey — DNS, TCP, TLS, HTTP, rendering — took maybe 300 milliseconds. But inside those 300ms, dozens of networking events occurred, each shaped by protocol design decisions made decades ago.
💡 Mental Model: The gap between "fast enough" and "too slow" in user experience is often measured in tens of milliseconds. Google found that a 100ms increase in search latency reduced revenue by 1%. Netflix found that increasing startup time by 1 second resulted in ~3% fewer streams started. Networking isn't infrastructure plumbing — it's a direct lever on user behavior and business outcomes.
Putting It in Code: Observing the Network
Theory is valuable, but let's ground this in something you can actually run. The following snippets show how the network concepts we just described manifest in real code.
Snippet 1: A simple TCP client-server exchange in Python
This demonstrates the connection primitives — sockets, ports, and the three-way handshake happening behind one function call:
## server.py — listens on a port and echoes messages back
import socket
HOST = '127.0.0.1' # Loopback address (localhost)
PORT = 65432 # Port to listen on (non-privileged ports are > 1023)
## AF_INET = IPv4, SOCK_STREAM = TCP (reliable, ordered bytes)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
server_socket.bind((HOST, PORT)) # Claim the address:port
server_socket.listen() # Start accepting connections (TCP handshake handled here)
print(f'Server listening on {HOST}:{PORT}')
conn, addr = server_socket.accept() # Block until a client connects
with conn:
print(f'Connected by {addr}')
while True:
data = conn.recv(1024) # Read up to 1024 bytes
if not data:
break # Client closed connection
conn.sendall(data) # Echo the data back
## client.py — connects and sends a message
import socket
HOST = '127.0.0.1'
PORT = 65432
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
# TCP three-way handshake happens inside connect()
client_socket.connect((HOST, PORT))
client_socket.sendall(b'Hello, network!') # b'' = bytes, not string
data = client_socket.recv(1024)
print(f'Received: {data.decode()}')
When connect() is called, Python's socket library performs the full TCP three-way handshake under the hood. You never see the SYN, SYN-ACK, ACK packets — they're handled by the OS's TCP/IP stack. This is exactly the kind of layering we'll explore in Section 2.
Snippet 2: Measuring DNS resolution time
In a system design interview, you should be able to reason about how long each network step takes. Here's how you can empirically observe DNS lookup latency:
import socket
import time
def measure_dns_lookup(hostname: str) -> float:
"""Returns DNS resolution time in milliseconds."""
start = time.perf_counter() # High-resolution timer
try:
# getaddrinfo is the full DNS resolution call
# Returns list of (family, type, proto, canonname, sockaddr) tuples
results = socket.getaddrinfo(hostname, 80) # Port 80 = HTTP
elapsed_ms = (time.perf_counter() - start) * 1000
ip_address = results[0][4][0] # Extract just the IP from first result
print(f'{hostname} → {ip_address} ({elapsed_ms:.2f}ms)')
return elapsed_ms
except socket.gaierror as e:
print(f'DNS lookup failed: {e}')
return -1
## Try a few hosts to see variance in DNS resolution time
for host in ['google.com', 'github.com', 'localhost']:
measure_dns_lookup(host)
## Example output:
## google.com → 142.250.80.46 (12.43ms) ← Cold lookup
## github.com → 140.82.114.4 (8.71ms)
## localhost → 127.0.0.1 (0.03ms) ← OS resolves immediately
Notice how localhost resolves almost instantly — it never leaves your machine. Remote hosts add 8-120ms of lookup time depending on caching. In Section 4, we'll build on this intuition to reason about cumulative latency across an entire request chain.
🤔 Did you know? A typical web page load involves not one but several DNS lookups — one for each unique hostname referenced in the page (CDN domains, analytics providers, font servers, etc.). On a slow mobile connection, these lookups can collectively add 500ms or more to page load time. This is precisely why DNS prefetching and preconnect hints exist in modern browsers.
What to Expect in This Lesson
Here's a quick orientation of the ground we'll cover and what you'll be able to do with it in an interview:
📋 Quick Reference Card: Lesson Sections and Interview Payoffs
| 📚 Section | 🔧 Core Concept | 🎯 Interview Payoff |
|---|---|---|
| 🔒 Section 2 | OSI & TCP/IP Models | Explain how data moves, justify protocol choices |
| 🌐 Section 3 | IPs, Ports, Sockets | Reason about service discovery, load balancing |
| ⚡ Section 4 | Latency, Bandwidth, Throughput | Size systems, identify bottlenecks, set SLAs |
| ⚠️ Section 5 | Common Pitfalls | Avoid mistakes that signal junior thinking |
Each section builds on the last. By Section 4, you won't just know what latency is — you'll be able to sketch latency budgets for a system design on a whiteboard and explain exactly where the milliseconds go.
⚠️ Common Mistake: Engineers often want to skip networking fundamentals and jump straight to "the interesting parts" of system design — databases, caching, message queues. But interviewers notice when a candidate can't explain why a cache reduces latency (hint: it's not magic — it's about avoiding a network round trip) or why you'd place servers in multiple regions (hint: the speed of light is a hard limit on RTT). Don't skip the foundation.
❌ Wrong thinking: "Networking is DevOps/infra territory. As a software developer, I just need to know HTTP."
✅ Correct thinking: "Every distributed system decision I make has networking implications. Understanding the network helps me design systems that are fast, resilient, and cost-efficient."
Your Starting Point: What You Likely Already Know
You don't come to this lesson empty-handed. Most developers already have working intuitions about the network — they just haven't formalized them. Let's surface what you probably already know:
🧠 You know that websites have addresses (URLs) and that something translates them to numbers — that's DNS 📚 You know that HTTP is how browsers and servers talk — that's the application layer 🔧 You know that some connections are encrypted (the padlock in your browser) — that's TLS 🎯 You know that slow internet makes apps feel sluggish — that's latency and bandwidth 🔒 You know that servers listen for connections on specific channels — those are ports
This lesson takes every one of those intuitions and turns them into precise, technical knowledge you can reason about, discuss, and design around. By the end, you won't just know that a CDN "makes things faster" — you'll be able to explain exactly which networking properties it exploits and when it makes architectural sense to use one.
💡 Pro Tip: In system design interviews, the candidates who stand out aren't necessarily the ones who know the most facts. They're the ones who can reason about tradeoffs. Networking fundamentals give you the vocabulary and mental models to reason clearly. When you say "I'd use UDP here because the cost of a retransmit is higher than the cost of an occasional dropped packet," you're demonstrating exactly the kind of judgment that gets candidates hired at senior levels.
The Road Ahead
Networking is a deep field. Entire careers are built around BGP routing, or TCP congestion control, or network security. This lesson isn't trying to turn you into a network engineer — it's trying to give you exactly the networking knowledge a software developer needs to excel at system design interviews and to build better distributed systems.
Think of what follows as a toolkit. Each concept is a tool. The better you understand each tool — not just its name, but when and why you'd reach for it — the more credibly and confidently you can navigate any system design conversation.
Ready? Let's start by building your mental model of how data actually travels across a network. That's next: the OSI and TCP/IP models, explained in plain language, without any of the rote memorization that makes most networking curricula feel like a chore.
The network is always there, underneath everything. It's time to finally see it clearly.
How Data Travels: The OSI and TCP/IP Models Explained
Before you can design a system that serves millions of users, you need a mental model of how a single byte of data gets from one machine to another. That journey is surprisingly intricate, and the engineers who built the internet organized that complexity into layered models — structured frameworks that break the problem of networking into smaller, manageable responsibilities. Two models dominate the conversation: the OSI model and the TCP/IP model. Understanding both — and knowing where one maps to the other — is foundational to reasoning about distributed systems in any serious technical interview.
Why Layered Models Exist
Imagine trying to design every component of network communication in one monolithic specification. You would need to simultaneously decide how electrical signals pulse across copper wire, how packets get addressed across continents, how connections stay reliable, and how web browsers format HTTP requests. That is an impossible design problem to solve as a whole. Layered models solve this by separating concerns: each layer has a single, well-defined job, and it communicates only with the layers directly above and below it. This means engineers designing routers don't need to understand HTTP, and web developers writing REST APIs don't need to understand how Wi-Fi encodes radio signals.
🎯 Key Principle: Each layer in a network model abstracts away the complexity of the layer below it, exposing only a clean interface to the layer above. This is the same principle behind good software architecture.
The OSI Model: Seven Layers From Wire to Application
The Open Systems Interconnection (OSI) model was defined by the International Organization for Standardization in 1984. It divides network communication into seven layers. While the OSI model is more of a conceptual reference than a strict implementation blueprint, it gives engineers a shared vocabulary for discussing where in the networking stack a problem originates.
Here is the full stack, from the bottom up:
┌─────────────────────────────────────────────┐
│ Layer 7 — Application │ HTTP, FTP, SMTP, DNS
├─────────────────────────────────────────────┤
│ Layer 6 — Presentation │ TLS/SSL, JPEG, ASCII
├─────────────────────────────────────────────┤
│ Layer 5 — Session │ NetBIOS, RPC sessions
├─────────────────────────────────────────────┤
│ Layer 4 — Transport │ TCP, UDP
├─────────────────────────────────────────────┤
│ Layer 3 — Network │ IP, ICMP, routing
├─────────────────────────────────────────────┤
│ Layer 2 — Data Link │ Ethernet, MAC addresses
├─────────────────────────────────────────────┤
│ Layer 1 — Physical │ Cables, radio waves, bits
└─────────────────────────────────────────────┘
Let's walk through each layer in plain terms.
Layer 1 — Physical: This is the raw medium: copper wire, fiber optic cable, Wi-Fi radio frequencies. The physical layer's job is to transmit raw bits (ones and zeros) across a physical medium. It doesn't know what those bits mean; it just moves them. When engineers talk about a "fiber optic link" or "10 Gigabit Ethernet," they are talking about Layer 1 characteristics.
Layer 2 — Data Link: The data link layer takes raw bits from Layer 1 and organizes them into frames — structured chunks of data with a header containing a MAC (Media Access Control) address. MAC addresses are hardware-level identifiers burned into your network interface card. This layer handles communication between devices on the same local network. Ethernet and Wi-Fi are Layer 2 protocols. Your home router uses Layer 2 to move data between devices connected to your local network.
Layer 3 — Network: This is where IP (Internet Protocol) lives, and it is the layer responsible for routing data across different networks. Instead of MAC addresses, Layer 3 uses IP addresses to identify source and destination machines anywhere on the internet. Routers operate at Layer 3, inspecting the destination IP address of each packet and deciding which direction to forward it.
Layer 4 — Transport: The transport layer manages end-to-end communication between processes on two machines. This is where TCP (Transmission Control Protocol) and UDP (User Datagram Protocol) live. TCP guarantees reliable, ordered delivery of data and handles re-transmission of lost packets. UDP sacrifices those guarantees for speed. The transport layer introduces the concept of ports, which allow multiple processes on the same machine to receive network data independently.
Layer 5 — Session: The session layer establishes, manages, and terminates communication sessions between applications. In practice, this layer's responsibilities are largely absorbed by the transport and application layers in modern systems. You will rarely hear engineers explicitly reference Layer 5 in day-to-day architecture discussions.
Layer 6 — Presentation: The presentation layer is responsible for data translation and formatting — converting data from the application's internal format into a format suitable for network transmission. TLS/SSL encryption operates conceptually at this layer, as does character encoding (ASCII, UTF-8) and data compression. When you see "HTTPS" in a URL, the encryption negotiated by TLS is a Layer 6 concern.
Layer 7 — Application: The application layer is where end-user protocols live: HTTP, FTP, SMTP, DNS. This is the layer your code most directly interacts with when building web services. When your browser makes an HTTP GET request or your service calls a REST API, you are operating at Layer 7.
🧠 Mnemonic: To remember the layers from bottom to top: "Please Do Not Throw Sausage Pizza Away" — Physical, Data Link, Network, Transport, Session, Presentation, Application.
The TCP/IP Model: Four Practical Layers
The OSI model is elegant for teaching, but real-world internet protocols don't map neatly onto all seven layers. The TCP/IP model (also called the Internet model) emerged from the practical implementation of the ARPANET and consolidates the OSI stack into four layers that reflect how protocols are actually organized in deployed systems.
┌──────────────────────────────┬──────────────────────────────┐
│ TCP/IP Model │ Maps to OSI Layers │
├──────────────────────────────┼──────────────────────────────┤
│ Application │ Layers 5, 6, 7 │
├──────────────────────────────┼──────────────────────────────┤
│ Transport │ Layer 4 │
├──────────────────────────────┼──────────────────────────────┤
│ Internet │ Layer 3 │
├──────────────────────────────┼──────────────────────────────┤
│ Network Access (Link) │ Layers 1, 2 │
└──────────────────────────────┴──────────────────────────────┘
The TCP/IP model collapses the OSI's Session, Presentation, and Application layers into a single Application layer, because in practice HTTP, FTP, and other protocols handle all three concerns themselves. Similarly, the Physical and Data Link layers are merged into a Network Access layer because they are typically handled together by the same hardware and drivers.
For system design interviews, the TCP/IP model is the more useful reference. When an interviewer asks "how does your service communicate with the database?", they want to hear you reason about the Application layer (which protocol — REST over HTTP, gRPC, raw TCP?) and the Transport layer (TCP for reliability, or UDP if you're designing a metrics system that can tolerate some loss). The lower layers are handled by the operating system and hardware infrastructure.
💡 Pro Tip: In interviews, when asked about protocols, anchor your answer in the TCP/IP layer that's relevant. Saying "we'll use HTTP/2 at the application layer over TCP at the transport layer" signals that you understand the full picture without getting lost in the weeds of Layer 1 and 2 details.
Encapsulation: How Data Gets Wrapped at Each Layer
One of the most important concepts to understand is encapsulation — the process by which each layer wraps the data it receives from the layer above with its own header (and sometimes a trailer), creating a new, larger unit of data. When the data arrives at the destination, each layer de-encapsulates by stripping its own header before passing the payload up to the next layer.
Here's what that looks like end-to-end when you send an HTTP request:
SENDER SIDE (encapsulation — headers added top-down):
Application Layer:
┌──────────────────────────────────────┐
│ HTTP Request (GET /index.html) │ ← Application Data
└──────────────────────────────────────┘
Transport Layer (TCP adds its header):
┌───────────┬──────────────────────────────────────┐
│ TCP Header│ HTTP Request │ ← TCP Segment
│(src/dst │ │
│ ports) │ │
└───────────┴──────────────────────────────────────┘
Internet Layer (IP adds its header):
┌──────────┬───────────┬──────────────────────────────────────┐
│ IP Header│ TCP Header│ HTTP Request │ ← IP Packet
│(src/dst │ │ │
│ IPs) │ │ │
└──────────┴───────────┴──────────────────────────────────────┘
Link Layer (Ethernet adds header + trailer):
┌────────┬──────────┬───────────┬──────────────────────┬──────┐
│ Eth Hdr│ IP Header│ TCP Header│ HTTP Request │ FCS │ ← Ethernet Frame
│(MACs) │ │ │ │ │
└────────┴──────────┴───────────┴──────────────────────┴──────┘
RECEIVER SIDE (de-encapsulation — headers removed bottom-up):
Link layer strips Ethernet header → IP layer strips IP header
→ TCP layer strips TCP header → Application receives HTTP request
This nested structure means each layer only needs to understand its own header format. A router at Layer 3 reads the IP header to forward the packet — it has no idea whether the payload is an HTTP request, a video stream, or a DNS query. This is the beauty of encapsulation: each layer is ignorant of the layers above it, which makes the whole system composable and extensible.
🤔 Did you know? The unit of data at each layer has a specific name: Application data is called a message, TCP wraps it into a segment, IP wraps that into a packet, and Ethernet wraps that into a frame. Interviewers appreciate candidates who use precise terminology.
Which Layers Matter Most for System Design?
As a backend engineer or system designer, you will spend almost no time thinking about Layer 1 or Layer 2 — those are the domain of network hardware engineers. Your decisions live predominantly in Layers 3, 4, and 7. Here's why each matters:
Layer 3 (Network/IP): IP addresses and routing are relevant when you design multi-region architectures, think about VPCs (Virtual Private Clouds), or configure firewall rules. When you discuss whether your services communicate over the public internet or a private network backbone, you're reasoning at Layer 3.
Layer 4 (Transport/TCP & UDP): The choice between TCP and UDP is one of the most consequential decisions in protocol design. TCP gives you reliability at the cost of latency (three-way handshake, retransmissions, congestion control). UDP gives you speed at the cost of guaranteed delivery. Real-time video calls use UDP. Financial transactions use TCP. This layer also defines ports, which are critical for any discussion of microservices, load balancers, or network security groups.
Layer 7 (Application): This is where most of your system design decisions live: REST vs. gRPC, HTTP/1.1 vs. HTTP/2 vs. HTTP/3, WebSockets for real-time communication, message queue protocols like AMQP. Load balancers that inspect HTTP headers operate at Layer 7 and are called Layer 7 load balancers — a term you'll encounter constantly in system design interviews.
💡 Real-World Example: When AWS describes an Application Load Balancer (ALB) vs. a Network Load Balancer (NLB), they're describing the OSI layer at which each operates. The ALB operates at Layer 7 — it can route requests based on URL paths or HTTP headers. The NLB operates at Layer 4 — it routes based on IP and TCP port alone, making it faster but less flexible.
A Code-Level Look: Raw Sockets at the Network Layer
To make these layers tangible, let's look at code. Most developers interact with networking through high-level abstractions like fetch() in JavaScript or requests in Python. But underneath those abstractions, the operating system is using sockets — endpoints for network communication that sit at the boundary between the application and the transport layer.
Here is a minimal raw TCP socket server in Python that operates directly at the socket interface between Layer 4 and Layer 7:
import socket
## AF_INET = IPv4 (Layer 3)
## SOCK_STREAM = TCP (Layer 4 — reliable, connection-oriented)
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
## Bind to all interfaces on port 8080
## Port numbers live at Layer 4 (Transport)
server_socket.bind(('0.0.0.0', 8080))
## Listen for incoming connections (backlog of 5)
server_socket.listen(5)
print("Server listening on port 8080...")
while True:
# Accept a connection — this completes the TCP 3-way handshake
client_socket, client_address = server_socket.accept()
print(f"Connection from {client_address}")
# Receive up to 1024 bytes of application-layer data
data = client_socket.recv(1024)
print(f"Received: {data.decode('utf-8')}")
# Send a response — Layer 7 data, transported by Layer 4 TCP
client_socket.send(b"Hello from the server!")
client_socket.close()
Notice how this code sits at the seam between layers. socket.AF_INET specifies we're working with IPv4 (a Layer 3 concern). socket.SOCK_STREAM selects TCP (Layer 4). The port 8080 is a Layer 4 concept. The actual data we send and receive is the application-layer payload — at this level, we're responsible for defining our own message format entirely.
Now compare that to the same communication handled by a Layer 7 HTTP library:
from http.server import HTTPServer, BaseHTTPRequestHandler
class SimpleHandler(BaseHTTPRequestHandler):
def do_GET(self):
# At Layer 7, the HTTP framework has already parsed:
# - The request method (GET)
# - The path (self.path)
# - Headers (self.headers)
# All the TCP connection management is invisible to us
self.send_response(200)
self.send_header('Content-Type', 'text/plain')
self.end_headers()
self.wfile.write(b'Hello from HTTP server!')
## HTTPServer internally creates and manages a socket,
## handles TCP connections, and parses HTTP — all hidden from us
server = HTTPServer(('0.0.0.0', 8080), SimpleHandler)
print("HTTP Server listening on port 8080...")
server.serve_forever()
The difference in abstraction level is striking. In the raw socket version, you handle every byte manually. In the HTTP server version, the framework has absorbed all of Layers 4 through 6 — connection management, data framing, header parsing — and hands you a clean Layer 7 interface.
For an even lower-level view, here's what it looks like to create a raw socket in Python that operates closer to Layer 3 — useful for tools like ping or custom network diagnostics:
import socket
import struct
## SOCK_RAW with IPPROTO_ICMP gives direct access to IP packets
## This requires root/administrator privileges on most systems
try:
raw_socket = socket.socket(
socket.AF_INET, # IPv4 — Layer 3
socket.SOCK_RAW, # Raw socket — bypass Layer 4 entirely
socket.IPPROTO_ICMP # We'll construct ICMP packets manually
)
print("Raw socket created — operating at Layer 3")
print("We are now responsible for constructing IP payloads ourselves.")
raw_socket.close()
except PermissionError:
print("Raw sockets require root privileges — the OS protects Layer 3 access.")
print("This illustrates that the OS enforces layer boundaries as a security measure.")
⚠️ Common Mistake: Many developers assume that "using TCP" means their application automatically handles all reliability concerns. In reality, TCP guarantees byte-stream delivery — but it doesn't define message boundaries. If you send two messages back-to-back over a raw TCP socket, the receiver might get them as one chunk or split across multiple reads. Application-layer protocols like HTTP solve this with Content-Length headers. This is a classic Layer 4 vs. Layer 7 confusion.
Putting It All Together: A Request's Full Journey
Let's trace a single HTTP request from your browser to a web server to see all the layers working in concert:
YOUR BROWSER WEB SERVER
(192.168.1.5) (93.184.216.34)
[Layer 7] You type example.com/index.html
│ HTTP GET request is created
▼
[Layer 4] TCP adds source port (e.g., 54321) and dest port (443)
│ If new connection: 3-way handshake occurs first (SYN/SYN-ACK/ACK)
▼
[Layer 3] IP adds source IP (192.168.1.5) and dest IP (93.184.216.34)
│ Routing table consulted to find next hop
▼
[Layer 2] Ethernet frame created with MAC of your router as destination
│
▼
[Layer 1] Bits transmitted over Wi-Fi radio waves
│
│ ~~~ travels across routers, cables, data centers ~~~
│ (Each router: reads IP header, forwards, re-encapsulates)
│
▼
[Layer 1] Bits arrive at server's NIC
▼
[Layer 2] Ethernet frame de-encapsulated, MAC header removed
▼
[Layer 3] IP packet de-encapsulated, IP header removed
▼
[Layer 4] TCP segment de-encapsulated, TCP header removed
▼
[Layer 7] HTTP request delivered to your web application
application reads GET /index.html and responds
The entire round-trip for a request to a nearby server often completes in under 50 milliseconds — the layers add overhead that is measured in microseconds, while the dominant cost is the physical propagation delay of signals traveling across hundreds of miles of cable at roughly two-thirds the speed of light.
📋 Quick Reference Card: OSI vs TCP/IP for System Designers
| 🔧 OSI Layer | 📚 TCP/IP Layer | 🎯 Protocols | 🔒 System Design Relevance |
|---|---|---|---|
| 7 Application | Application | HTTP, gRPC, WebSocket, DNS | API design, protocol choice |
| 6 Presentation | Application | TLS, encoding | Encryption, serialization |
| 5 Session | Application | RPC sessions | Connection management |
| 4 Transport | Transport | TCP, UDP | Reliability vs. latency tradeoffs |
| 3 Network | Internet | IP, ICMP | Routing, VPCs, firewalls |
| 2 Data Link | Network Access | Ethernet, Wi-Fi | Rarely directly relevant |
| 1 Physical | Network Access | Cables, fiber, radio | Almost never directly relevant |
💡 Mental Model: Think of the layered model as a restaurant. The customer (your application) interacts only with the waiter (Layer 7). The waiter communicates with the kitchen (Layers 4–6). The kitchen uses the restaurant's plumbing, electricity, and building structure (Layers 1–3). Each part of the restaurant is unaware of the internals of the others — they only communicate through defined interfaces.
Building this mental model of layered communication is the foundation for everything that follows. When you understand that TCP is a Layer 4 protocol that sits below HTTP, you immediately understand why switching from HTTP to gRPC still uses TCP underneath — and why HTTP/3 made the radical choice to move to UDP-based QUIC to eliminate TCP's head-of-line blocking. Every system design choice involving networking connects back to these layers.
IP Addresses, Ports, and Sockets: The Building Blocks of Network Communication
Every distributed system—whether it's a chat application serving millions of users or a microservices architecture running inside a data center—ultimately boils down to processes on different machines sending bytes to each other. Before you can design those systems intelligently, you need to understand the three primitives that make machine-to-machine communication possible: IP addresses, ports, and sockets. These aren't just low-level implementation details you can safely ignore. They appear in every meaningful system design conversation, from "how does your load balancer route traffic?" to "how does your service discovery mechanism work?" and "what happens when you run out of ephemeral ports under high load?"
This section builds that foundation from the ground up, using concrete code to show you exactly what happens when two processes communicate over a network.
IP Addresses: Naming Every Device on the Network
An IP address is a numerical label assigned to each device connected to a network that uses the Internet Protocol for communication. Think of it as a postal address for your machine—without it, there's no way for a packet of data to know where to go.
IPv4: The Address Space We Outgrew
IPv4 (Internet Protocol version 4) uses 32-bit addresses, written in the familiar dotted-decimal notation you've seen countless times: 192.168.1.1. Each of the four numbers (called octets) represents 8 bits, ranging from 0 to 255. With 32 bits total, IPv4 can represent 2³² addresses—about 4.3 billion unique addresses.
That sounds like a lot until you realize the modern internet connects smartphones, laptops, smart TVs, IoT sensors, servers, and containers—often multiple addresses per household or data center. The world ran out of unallocated IPv4 addresses in 2011. This scarcity has driven major architectural decisions you'll encounter in system design: NAT (Network Address Translation), private IP ranges, and eventually the migration to IPv6.
💡 Real-World Example: When you design a system running on AWS, your EC2 instances get a private IP from the 10.0.0.0/8 range within the VPC and a separate public IP for external traffic. Understanding that these are different addresses—and that the public IP goes through NAT—explains why a server can't just "see" its own public IP from inside the instance.
Reserved and Private IP Ranges
Not all IPv4 addresses are used on the public internet. Several ranges are reserved for private networks:
Private Ranges (RFC 1918):
10.0.0.0 – 10.255.255.255 (10.0.0.0/8) ~16.7 million addresses
172.16.0.0 – 172.31.255.255 (172.16.0.0/12) ~1 million addresses
192.168.0.0 – 192.168.255.255 (192.168.0.0/16) ~65,000 addresses
Special Purpose:
127.0.0.0 – 127.255.255.255 (loopback, "localhost")
0.0.0.0 ("any" / unspecified address)
CIDR Notation: Describing Ranges Compactly
CIDR (Classless Inter-Domain Routing) notation lets you describe a block of IP addresses using a single expression: an IP address followed by a slash and a prefix length. The prefix length tells you how many bits are fixed (the network part) and how many bits are free (the host part).
CIDR Example: 192.168.1.0/24
192 . 168 . 1 . 0
1100 0000 . 1010 1000 . 0000 0001 . 00000000
|<---------- 24 fixed bits -------->|<--8-->|
Network address: 192.168.1.0
Broadcast: 192.168.1.255
Usable hosts: 192.168.1.1 – 192.168.1.254 (254 addresses)
Why does CIDR matter for system design? When you configure a VPC subnet, a security group rule ("allow traffic from 10.0.0.0/8"), or a load balancer's IP allowlist, you're using CIDR notation. Understanding that /16 gives you 65,534 hosts while /28 gives you only 14 hosts helps you make sensible infrastructure sizing decisions.
🧠 Mnemonic: Think of the slash number as "how many bits are locked." /24 locks 24 bits, leaving 8 free, so you get 2⁸ = 256 addresses (254 usable). /16 locks 16 bits, leaving 16 free: 2¹⁶ = 65,536 addresses.
IPv6: The Address Space Built for the Future
IPv6 uses 128-bit addresses, written in eight groups of four hexadecimal digits separated by colons: 2001:0db8:85a3:0000:0000:8a2e:0370:7334. With 2¹²⁸ possible addresses—approximately 340 undecillion (3.4 × 10³⁸)—IPv6 was designed so we'd never run out again.
IPv6 also introduces built-in features that required workarounds in IPv4: better multicast support, mandatory IPSec support in early drafts, and stateless address autoconfiguration. For system design purposes, the key takeaways are:
- 🧠 IPv6 eliminates the need for NAT in most cases, enabling true end-to-end connectivity
- 📚 Modern cloud providers (AWS, GCP, Azure) support dual-stack configurations (both IPv4 and IPv6)
- 🔧 Load balancers and CDNs often terminate IPv6 externally and translate to IPv4 internally, since backend infrastructure still lags in full IPv6 adoption
🤔 Did you know? The loopback address in IPv6 is ::1—equivalent to IPv4's 127.0.0.1. The :: is shorthand for a contiguous block of zeroes.
Ports: Distinguishing Services on a Single Host
An IP address gets your data to the right machine. But a modern server might be running a web server, a database, an SSH daemon, and a metrics exporter all at the same time. How does the operating system know which process should receive an incoming packet?
That's what ports are for. A port is a 16-bit unsigned integer (ranging from 0 to 65,535) that identifies a specific process or service on a host. When a packet arrives, the OS uses the combination of IP address + port to route it to the correct process.
Incoming packet destination: 203.0.113.45 : 443
| |
IP Address Port
(which host) (which service)
Well-Known, Registered, and Ephemeral Ports
Ports are divided into three ranges by IANA (Internet Assigned Numbers Authority):
📋 Quick Reference Card: Port Number Ranges
| 🔒 Range | 📚 Name | 🎯 Usage |
|---|---|---|
| 0 – 1023 | Well-Known Ports | Reserved for system services; require root/admin to bind |
| 1024 – 49151 | Registered Ports | Registered for specific applications, user-bindable |
| 49152 – 65535 | Ephemeral Ports | Dynamically assigned by OS for outgoing client connections |
Here are the well-known port numbers that come up constantly in system design:
20/21 – FTP (data / control)
22 – SSH
25 – SMTP (email)
53 – DNS
80 – HTTP
443 – HTTPS
3306 – MySQL
5432 – PostgreSQL
6379 – Redis
27017 – MongoDB
2181 – ZooKeeper
9092 – Apache Kafka
Ephemeral Ports: A Hidden Scalability Constraint
⚠️ Common Mistake: Many developers forget about ephemeral port exhaustion as a real scalability limit.
When your application makes an outgoing TCP connection, the OS assigns a random ephemeral port from the range above to represent your end of the connection. On Linux, this range is typically 32768–60999 by default—about 28,000 available ports. If your service is making thousands of short-lived connections per second (think: a proxy or an API gateway hitting a backend), you can exhaust this pool, causing connect() calls to fail with EADDRNOTAVAIL.
💡 Real-World Example: This is one reason why connection pooling matters so much in database client libraries. Instead of opening a new TCP connection per query (which burns an ephemeral port and pays TCP handshake latency), you maintain a pool of persistent connections and reuse them.
Sockets: The Programming Abstraction for Network Connections
Now that we have addresses (IPs) and service identifiers (ports), we need a programming abstraction to actually create and use network connections. That abstraction is the socket.
A socket is one endpoint of a two-way communication link between two programs running on a network. In Unix-derived operating systems (Linux, macOS), sockets follow the "everything is a file" philosophy—a socket is represented as a file descriptor that you can read from and write to, just like a regular file.
A socket is uniquely identified by a 5-tuple:
(Protocol, Source IP, Source Port, Destination IP, Destination Port)
Example active connection:
(TCP, 192.168.1.10, 54321, 93.184.216.34, 443)
| src | ephemeral | dst (example.com) | HTTPS |
This 5-tuple is how the kernel differentiates between thousands of simultaneous TCP connections. Two connections to the same server on the same port are distinguished by different source ports.
🎯 Key Principle: A socket isn't a connection—it's an endpoint. A server socket in the LISTEN state has no peer yet. A connection only exists once both sides have completed the handshake and each side holds a connected socket pointing to the other.
Socket Types
- SOCK_STREAM (TCP): Provides a reliable, ordered, byte-stream connection. What you use for HTTP, database queries, SSH.
- SOCK_DGRAM (UDP): Connectionless, unreliable datagrams. What you use for DNS queries, video streaming, gaming.
- SOCK_RAW: Direct access to IP layer—used for ping (ICMP) and custom protocol implementations.
Hands-On: Building a Client-Server Connection in Python
The best way to internalize these concepts is to see them in working code. Let's build a minimal TCP server and client from scratch.
The Server Side
import socket
## AF_INET = IPv4 address family
## SOCK_STREAM = TCP (reliable, connection-oriented)
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
## SO_REUSEADDR lets us rebind to this port quickly after restart
## Without this, you'd get "Address already in use" for ~60 seconds
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
## Bind to all interfaces on port 9000
## '0.0.0.0' means "accept connections on any network interface"
server_socket.bind(('0.0.0.0', 9000))
## Start listening; allow up to 5 pending connections in the backlog queue
server_socket.listen(5)
print("Server listening on 0.0.0.0:9000")
while True:
# accept() blocks until a client connects
# Returns a new socket for THIS connection + the client's address
conn, client_address = server_socket.accept()
print(f"Connection from {client_address[0]}:{client_address[1]}")
# Read up to 1024 bytes from the client
data = conn.recv(1024)
if data:
print(f"Received: {data.decode('utf-8')}")
# Echo it back with a prefix
conn.sendall(b"Echo: " + data)
conn.close() # Close this connection's socket (not the server socket)
Notice that accept() returns a brand new socket—conn—which represents the specific connection to this one client. The original server_socket stays in its LISTEN state, ready to accept the next client. This is the key insight: the listening socket and the connected socket are different objects.
The Client Side
import socket
## Create a TCP socket (same parameters as server)
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
## connect() performs the TCP three-way handshake with the server
## This is where SYN, SYN-ACK, ACK happens under the hood
client_socket.connect(('127.0.0.1', 9000))
print("Connected to server at 127.0.0.1:9000")
## Send a message - encode to bytes because sockets work with raw bytes
message = "Hello from the client!"
client_socket.sendall(message.encode('utf-8'))
## Wait for the server's response
response = client_socket.recv(1024)
print(f"Server said: {response.decode('utf-8')}")
## Check what local port the OS assigned us (ephemeral port)
local_ip, local_port = client_socket.getsockname()
print(f"My ephemeral port was: {local_port}")
client_socket.close()
When you run the client, the OS picks an ephemeral port (say, 54891) automatically. The connection is identified by the 5-tuple (TCP, 127.0.0.1, 54891, 127.0.0.1, 9000). If you open a second client simultaneously, it gets a different ephemeral port, creating a distinct connection even though both go to the same server IP and port.
A UDP Socket for Comparison
import socket
## UDP server - notice SOCK_DGRAM instead of SOCK_STREAM
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind(('0.0.0.0', 9001))
print("UDP server listening on port 9001")
## recvfrom() receives one datagram + the sender's address
## There is NO accept() call - UDP is connectionless
data, sender_address = server.recvfrom(4096)
print(f"Got datagram from {sender_address}: {data.decode()}'")
## Reply directly to the sender's address
server.sendto(b"Got your datagram!", sender_address)
server.close()
The contrast is stark: UDP has no connect(), no accept(), no persistent connection. Every recvfrom() call can come from a different sender. This statelessness is exactly why UDP is used for DNS—you send one question, you get one answer, connection overhead would be wasteful.
The Client-Server Communication Model
The code above demonstrates the canonical client-server model, which underlies virtually every distributed system you'll design. Let's make the flow explicit:
SERVER CLIENT
| |
| socket() |
| bind(ip, port) |
| listen() |
| | socket()
| | connect(server_ip, server_port)
|<------- TCP SYN ---------------- |
|-------- TCP SYN-ACK -----------> |
|<------- TCP ACK ---------------- |
| accept() [unblocks] |
| |
|<------- REQUEST (bytes) -------- | send()
| recv() |
| |
| [process request] |
| |
|-------- RESPONSE (bytes) ------> | recv()
| send() |
| |
| close() | close()
| |
This request-response cycle is the heartbeat of the internet. HTTP runs on top of this. REST APIs, gRPC, WebSocket handshakes—all of them begin with this same TCP handshake sequence before any application-level protocol takes over.
🎯 Key Principle: The client always initiates the connection; the server always waits for connections. This asymmetry explains why servers need public, stable IP addresses and why clients can sit behind NAT with private IPs—the server never needs to reach back to start a conversation.
Scaling the Model: What Changes at Scale
The single-threaded server in our example handles one client at a time. In production, you need to handle thousands of concurrent connections. There are three classic approaches:
- 🔧 Multi-process model (Apache prefork): Fork a new process per connection. Simple, isolated, but expensive in memory.
- 🔧 Multi-threaded model (Java thread-per-request): Spawn a thread per connection. More efficient than processes but still limited by thread overhead.
- 🔧 Event-driven / async I/O model (Nginx, Node.js): A single thread uses OS primitives (
epollon Linux,kqueueon macOS) to monitor thousands of sockets simultaneously. One thread handles tens of thousands of concurrent connections.
This architectural choice—sync vs. async I/O—is a fundamental system design question. When an interviewer asks "how would you handle 100,000 concurrent connections?", the answer involves understanding that each socket is cheap (a file descriptor), but blocking threads waiting on those sockets are expensive.
⚠️ Common Mistake: Assuming that "more threads = more throughput." Beyond a certain point, thread context switching overhead dominates. This is why Nginx dramatically outperforms multi-threaded servers under high concurrency—it's the C10K problem and the reason event loops exist.
💡 Mental Model: Think of a multi-threaded server like a restaurant where every customer gets their own dedicated waiter who stands there watching them eat. An event-driven server is one waiter managing a whole section, checking in on each table only when something needs attention.
Bringing It Together: Addressing at Scale
Now you can connect the dots for a system design interview. When you're asked to design a URL shortener, a message queue, or a ride-sharing backend, here's how these primitives surface:
- Your load balancer has a public IP and listens on port 443. Clients connect to it. The load balancer maintains a pool of sockets to your backend servers, forwarding requests while hiding internal addressing.
- Your backend services each have private IPs (RFC 1918 ranges) within a VPC subnet described by CIDR notation. They listen on application-specific ports (e.g., 8080 for HTTP).
- Your database server binds to port 5432 (PostgreSQL) but only on its private interface—never exposed to the public internet.
- Service-to-service calls burn ephemeral ports on the client side. High-throughput services use connection pools to avoid exhausting them.
📋 Quick Reference Card: Addressing in a Typical 3-Tier Architecture
| 🔒 Layer | 📚 IP Type | 🎯 Port Example | 🔧 Socket Role |
|---|---|---|---|
| Load Balancer | Public IP | 443 (HTTPS) | Accepts client connections |
| App Server | Private IP (10.x.x.x) | 8080 (HTTP) | Accepts from LB; connects to DB |
| Cache (Redis) | Private IP | 6379 | Accepts from app servers only |
| Database | Private IP | 5432 | Accepts from app servers only |
Every arrow in a system design diagram ultimately corresponds to one process opening a socket to another. When you can think at that level of concreteness—knowing that a connection has a source port, a destination port, and crosses network boundaries with specific addressing rules—your designs become far more grounded and your interview answers far more credible.
💡 Pro Tip: In a system design interview, when you draw a connection between two services, be ready to state: what port the server listens on, whether the connection is persistent or short-lived (connection pool vs. per-request), and what happens to the connection under failure conditions. That level of specificity separates strong candidates from average ones.
Latency, Bandwidth, and Throughput: Reasoning About Network Performance
Every system design decision you make sits on top of a physical reality: data has to travel. Electrons move through copper, photons pulse through fiber, radio waves propagate through air — and none of it is instantaneous. When you design a caching layer, choose a replication strategy, or decide whether to compress a payload, you are making a bet about network performance. Interviewers know this, and they will probe whether your architecture reflects an accurate mental model of how fast data actually moves. This section arms you with the three foundational metrics — latency, bandwidth, and throughput — and teaches you to reason with them the way experienced engineers do.
Defining the Three Core Metrics
Before you can reason about network performance, you need precise definitions. These three terms are often used loosely in conversation, but conflating them in an interview signals imprecise thinking.
Latency is the time it takes for a single unit of data to travel from a source to a destination. Think of it as delay. It is typically measured in milliseconds (ms) or microseconds (µs) and is fundamentally a measure of time, not volume. If you send a single byte from New York to London and it arrives 75ms later, the latency of that trip is 75ms.
Bandwidth is the maximum capacity of a network link — the theoretical ceiling on how much data can flow through it per unit of time. It is measured in bits per second: megabits per second (Mbps), gigabits per second (Gbps), and so on. A highway analogy works well here: bandwidth is the number of lanes on the highway.
Throughput is the actual amount of data successfully transferred per unit of time under real-world conditions. Throughput is always less than or equal to bandwidth. It is what you actually achieve, not what you theoretically could achieve. Back to the highway: throughput is how many cars actually get through per hour, accounting for traffic jams, accidents, and toll booths.
Latency: Source ----[75ms delay]----> Destination
(measures TIME for one unit of data)
Bandwidth: Source =====[1 Gbps pipe]=====> Destination
(measures MAX CAPACITY of the link)
Throughput: Source ----[650 Mbps actual]---> Destination
(measures REAL DATA transferred per second)
^
| Always ≤ Bandwidth
| (congestion, packet loss, protocol overhead)
💡 Mental Model: Imagine sending letters through a pneumatic tube system. Latency is how long it takes one letter to arrive. Bandwidth is how wide the tube is (how many letters fit side by side). Throughput is how many letters actually get delivered per minute after accounting for tube blockages and processing time.
🎯 Key Principle: Low latency and high bandwidth are independent properties. You can have a high-bandwidth, high-latency link (a satellite internet connection can transfer gigabytes but has ~600ms round-trip latency) or a low-latency, low-bandwidth link (a local serial connection). Never assume one implies the other.
How Latency Accumulates in Distributed Systems
Latency is not a single fixed number — it is the sum of several contributing factors, and understanding each one helps you reason about where optimization is possible.
Propagation delay is the time light (or electricity) takes to physically travel the distance between two points. This is governed by physics. Light travels through fiber optic cable at roughly two-thirds the speed of light in a vacuum, or about 200,000 km/s. That means a round trip from New York to London (approximately 5,600 km one way) has a minimum propagation delay of around 56ms — and that is before any processing happens.
Transmission delay is the time required to push all bits of a packet onto the wire. For a 1,500-byte Ethernet frame on a 1 Gbps link, this is about 12 microseconds — negligible for most purposes. On a slower link or with larger payloads, it becomes meaningful.
Processing delay accumulates at every network device — routers, switches, load balancers, firewalls — that must inspect and forward each packet. Each hop adds a small amount of processing time, typically microseconds to low milliseconds per hop.
Queuing delay is what happens when a router or switch receives packets faster than it can forward them. They pile up in a buffer. Under heavy congestion, queuing delay can dominate and cause latency to spike dramatically — this is the main cause of network jitter.
Client Server
| |
|---[Propagation: 40ms]------------------------>|
| [Processing at 6 routers: ~3ms total] |
| [Queuing under congestion: 0–50ms variable] |
|<--[Same delays on return path]----------------|
|
Total RTT: ~86ms (no congestion) to ~186ms (congested)
⚠️ Common Mistake: Engineers often forget that round-trip time (RTT) is what most applications experience, not one-way latency. When your application sends a request and waits for a response, you pay the latency cost twice. A 40ms one-way delay becomes an 80ms RTT minimum, before any server processing time.
The Latency Numbers Every Engineer Should Know
Jeff Dean, one of Google's most celebrated engineers, popularized the habit of keeping a table of latency numbers in your head. These numbers change slightly as hardware evolves, but the orders of magnitude remain stable and are what interviewers expect you to have internalized.
📋 Quick Reference Card: Latency Numbers You Must Know
| ⏱️ Operation | 📊 Approximate Latency |
|---|---|
| 🔧 L1 cache reference | ~0.5 ns |
| 🔧 L2 cache reference | ~7 ns |
| 🧠 RAM read | ~100 ns |
| 📚 SSD random read | ~100 µs (100,000 ns) |
| 📚 HDD random read | ~10 ms (10,000,000 ns) |
| 🌐 Same datacenter round trip | ~0.5 ms |
| 🌐 Cross-region (e.g., US East → US West) | ~40–70 ms |
| 🌐 Cross-continent (e.g., US → Europe) | ~80–150 ms |
| 🛰️ Satellite internet round trip | ~500–600 ms |
🤔 Did you know? RAM is approximately 1,000x faster than an SSD, and an SSD is approximately 100x faster than an HDD. A cross-datacenter network round trip is slower than reading from an SSD but faster than reading from a spinning disk. This is why in-memory caching (Redis, Memcached) is so transformative — serving data from RAM instead of triggering a cross-service network call can be a 1,000x latency improvement.
🧠 Mnemonic: To remember the orders of magnitude, use "ns, µs, ms" with powers of 1,000: cache is nanoseconds, disk is microseconds (SSD) to milliseconds (HDD), and the network is milliseconds. Each step up is roughly 1,000x slower.
The practical implication: every time your code makes a synchronous network call inside a loop, you are paying 0.5ms to 150ms per iteration. A loop that makes 100 cross-datacenter calls accumulates 5 to 15 seconds of network wait time. This is why batch requests, connection pooling, and asynchronous I/O exist.
Here is a Python snippet that demonstrates measuring actual round-trip latency to a server, which is useful for building intuition during development:
import socket
import time
def measure_rtt(host: str, port: int, num_samples: int = 10) -> dict:
"""
Measure round-trip time to a TCP endpoint by timing a connect + close cycle.
This approximates network RTT without application-layer overhead.
"""
latencies = []
for _ in range(num_samples):
start = time.perf_counter() # High-resolution timer
# Attempt a TCP connection (SYN → SYN-ACK → ACK = 1 RTT)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5.0)
try:
sock.connect((host, port))
finally:
sock.close()
end = time.perf_counter()
rtt_ms = (end - start) * 1000 # Convert seconds to milliseconds
latencies.append(rtt_ms)
return {
"min_ms": round(min(latencies), 2),
"max_ms": round(max(latencies), 2),
"avg_ms": round(sum(latencies) / len(latencies), 2),
}
## Example: measuring RTT to a public HTTP server
results = measure_rtt("example.com", 80)
print(f"RTT Stats: {results}")
## Typical output for a US-based server from US:
## RTT Stats: {'min_ms': 12.3, 'avg_ms': 14.1, 'max_ms': 19.8}
This code establishes a TCP connection, which requires one RTT (the SYN/SYN-ACK/ACK handshake), giving you a close approximation of raw network latency to a host. Notice how the max can be significantly higher than the min — that variability is queuing delay at work.
How Bandwidth and Throughput Bottlenecks Drive Architecture Decisions
Bandwidth constraints are less visible than latency in day-to-day engineering but drive some of the most impactful architectural decisions at scale. A single modern application server might have a 10 Gbps network interface — that sounds enormous, but at scale it becomes a genuine constraint.
Consider: 10 Gbps = 1.25 GB/s. If your service returns 50 KB responses and handles 25,000 requests per second, that is 25,000 × 50,000 bytes = 1.25 GB/s — you have just saturated a 10 Gbps NIC. Response size matters.
Caching is the most direct response to bandwidth constraints. If 80% of your traffic requests the same 1,000 pieces of content (a common distribution following Zipf's law), placing those items in a CDN or in-memory cache means you serve them without touching your origin servers or consuming internal bandwidth. The architectural decision to add a caching layer is fundamentally a decision about bandwidth economics.
Data compression is another lever. HTTP's Content-Encoding: gzip or br (Brotli) can reduce JSON or text payloads by 60–80%. If you are transferring 500 GB of log data from one datacenter to another daily, compression turns that into ~150 GB, reducing both transfer time and egress costs (cloud providers charge per GB transferred across regions).
Replication placement is a latency-bandwidth tradeoff. Synchronous replication to a replica in another region guarantees no data loss but adds the cross-region RTT to every write. Asynchronous replication removes the write latency cost but introduces the possibility of data loss if the primary fails before the replica catches up. The bandwidth of the replication link determines how far behind the replica can fall under heavy write load.
Here is a snippet showing how you might calculate bandwidth requirements programmatically — a skill useful in back-of-the-envelope interview estimates:
def estimate_bandwidth_requirements(
requests_per_second: int,
avg_response_size_kb: float,
compression_ratio: float = 1.0, # 1.0 = no compression, 0.3 = 70% reduction
) -> dict:
"""
Estimate outbound bandwidth requirements for a service.
Returns results in Mbps and Gbps for easy comparison with NIC specs.
"""
# Effective bytes per response after compression
effective_response_kb = avg_response_size_kb * compression_ratio
# Total bytes per second
bytes_per_second = requests_per_second * effective_response_kb * 1024
# Convert to bits (network bandwidth is measured in bits, not bytes)
bits_per_second = bytes_per_second * 8
mbps = bits_per_second / 1_000_000
gbps = bits_per_second / 1_000_000_000
return {
"effective_response_kb": round(effective_response_kb, 2),
"bandwidth_mbps": round(mbps, 2),
"bandwidth_gbps": round(gbps, 4),
"saturates_1gbps_nic": gbps > 1.0,
"saturates_10gbps_nic": gbps > 10.0,
}
## Scenario: API returning 50KB responses at 20,000 RPS, with gzip (70% reduction)
result = estimate_bandwidth_requirements(
requests_per_second=20_000,
avg_response_size_kb=50,
compression_ratio=0.30, # gzip reduces to 30% of original size
)
print(result)
## Output:
## {'effective_response_kb': 15.0, 'bandwidth_mbps': 2400.0,
## 'bandwidth_gbps': 2.4, 'saturates_1gbps_nic': True, 'saturates_10gbps_nic': False}
This kind of calculation — estimating whether a proposed design will saturate available resources — is exactly what interviewers are looking for in a back-of-the-envelope estimation question. Notice how compression changes a potentially problematic 8 Gbps requirement into a manageable 2.4 Gbps.
💡 Pro Tip: In interviews, always distinguish between inbound and outbound bandwidth. Read-heavy services (social feeds, CDN origins) burn outbound bandwidth. Write-heavy services (log ingestion, media upload) burn inbound bandwidth. Replication burns both. Naming the bottleneck precisely signals engineering maturity.
Practical Scenario: Estimating Network Costs for a File-Sharing Feature
Let's put all three concepts together with a realistic design scenario. Imagine you are asked to design a file-sharing feature for a collaboration tool — think Google Drive or Dropbox at a fraction of the scale. The interviewer tells you:
- 10 million active users
- Average file size: 5 MB (mix of documents, images, small videos)
- Each user uploads 2 files per day and downloads 10 files per day
- Files must be accessible across two geographic regions: US and Europe
Let's reason through the network performance implications step by step.
Step 1: Upload Throughput Requirements
Upload volume per day:
10,000,000 users × 2 files × 5 MB = 100,000,000 MB/day
= 100 TB/day
Average upload throughput:
100 TB/day ÷ 86,400 seconds/day ≈ 1.16 GB/s
= ~9.3 Gbps inbound bandwidth required at origin
This immediately tells you that a single server with a 10 Gbps NIC is almost fully saturated on average — and traffic is never perfectly average. Peak hours might be 3–5× the average, pushing you to ~30–45 Gbps. You need horizontal scaling and a distributed upload architecture.
Step 2: Download Latency Requirements
Users expect files to feel fast. A 5 MB file should download in under 2 seconds for a good user experience. That requires a sustained throughput of at least 2.5 MB/s to the end user — achievable on most broadband connections, but not over a lossy mobile connection or if the origin is far away.
User in Frankfurt downloading from US-East origin:
Physical RTT: ~100ms
TCP slow start: several RTTs before full speed
5 MB file at effective 1 MB/s (realistic on congested path): ~5 seconds
User in Frankfurt downloading from European edge CDN:
Physical RTT: ~10ms
5 MB file at effective 5 MB/s (nearby, low congestion): ~1 second ✅
The latency arithmetic makes the case for CDN edge caching almost automatically. You don't need to argue abstractly that CDNs are good — you show that serving from origin to European users adds 4+ seconds of perceived download time.
Step 3: Cross-Region Replication Bandwidth
If a user in the US uploads a file, and users in Europe need to access it, the file must replicate across the Atlantic.
Cross-region replication cost:
100 TB/day uploaded × assume 30% accessed cross-region = 30 TB/day cross-region
30 TB ÷ 86,400 seconds ≈ 350 MB/s cross-region sustained bandwidth
= ~2.8 Gbps dedicated cross-Atlantic bandwidth
At cloud pricing (~$0.02/GB for inter-region transfer):
30 TB/day × $0.02/GB × 1,000 GB/TB = $600/day = ~$18,000/month
This is where compression becomes a business decision, not just a technical nicety. Compressing files before replication (for compressible types like documents) could cut replication costs by 50–60%, saving $9,000–$11,000/month. The engineering time to implement compression pays for itself in weeks.
System Architecture informed by this analysis:
[US Users] [EU Users]
| |
v v
[US Upload Cluster] [EU Edge CDN]
(10+ servers, load balanced) (cached copies)
| ^
|--[Async replication, compressed]------|
|
v
[US Origin Storage]<----[US CDN]<---[US Users downloading]
💡 Real-World Example: Dropbox famously re-architected their storage infrastructure (migrating off AWS S3) partly to control the egress bandwidth costs that come with serving billions of file downloads per day. At scale, the per-GB cost of bandwidth becomes a dominant line item in infrastructure budgets. Understanding bandwidth economics is not just a technical skill — it is a business skill.
⚠️ Common Mistake: Many candidates design systems where every file download hits the origin storage directly. This burns bandwidth at the most expensive point (origin egress), maximizes latency for distant users, and creates a single bottleneck that kills performance under peak load. Always ask yourself: where in the network should data live to minimize both latency and bandwidth costs for the access patterns I expect?
Bringing It Together: A Framework for Performance Reasoning
When approaching any system design problem, train yourself to run through this mental checklist for network performance:
🎯 Latency questions:
- Where are my users relative to my servers? Do I need edge locations?
- How many synchronous network hops does a single user request require? (Each service-to-service call adds RTT)
- Am I making network calls inside loops or sequentially when I could batch or parallelize?
📚 Bandwidth questions:
- What is my peak data volume, and does it fit through the available pipes?
- Can I reduce payload size through compression, pagination, or field filtering?
- Are my replication and backup jobs competing with user traffic for the same links?
🔧 Throughput questions:
- Is my actual throughput close to my theoretical bandwidth? If not, where is the loss? (Packet loss? Protocol overhead? Retransmissions?)
- Do I have the right number of parallel connections? (TCP throughput is limited by window size; multiple connections can increase aggregate throughput)
- Am I measuring throughput under realistic load, or only in ideal conditions?
❌ Wrong thinking: "I have a 1 Gbps network, so I can transfer 1 Gb per second."
✅ Correct thinking: "I have a 1 Gbps link. After protocol overhead, TCP congestion control, and competing traffic, I'll realistically achieve 600–800 Mbps sustained throughput, and I need to plan my architecture around that real number, not the theoretical maximum."
The engineers who impress interviewers are not the ones who know that latency exists — everyone knows that. They are the ones who can quantify the impact, trace it to a specific architectural decision, and propose a concrete solution grounded in the physics of how data actually moves. That habit of mind, built on a foundation of precise definitions and internalized reference numbers, is what this section has given you.
Common Networking Pitfalls in System Design
Even experienced engineers stumble when reasoning about networking in system design interviews. The mistakes are rarely about forgetting a formula or missing a definition — they are deeper, more conceptual errors baked into how we mentally model distributed systems. Networks are invisible, asynchronous, and unreliable by nature, and our brains are not wired to think that way intuitively. This section catalogs the most common traps and shows you exactly how to sidestep them.
Pitfall 1: Assuming the Network Is Reliable
The single most pervasive mistake in system design is treating a network call like a function call. When you call a local function, it either returns a value or throws an exception — there is no ambiguity. Network calls do not work this way. A request can be sent but never received. A response can be sent but never arrive. The server can process the request and crash before acknowledging it. This gray zone is what engineers call a partial failure.
Partial failures occur when one component of a distributed system fails while others continue operating. The calling service has no immediate way to know whether the remote service received the request, processed it, or dropped it entirely. This uncertainty forces you to design explicit strategies for every remote call.
⚠️ Common Mistake: Designing a payment processing system where the client retries a failed charge without any idempotency key. If the first request actually succeeded but the acknowledgment was lost, the retry charges the customer twice.
The two critical mechanisms for handling unreliable networks are timeouts and retries, and they must be designed together.
- Timeouts define how long a caller will wait before assuming the remote call has failed. Without a timeout, a slow downstream service can exhaust all available threads in your application, causing a cascade failure known as a thread starvation or cascading timeout.
- Retries attempt the operation again after a failure, but naive retries can amplify load on an already struggling service — a phenomenon called a retry storm.
The solution is exponential backoff with jitter: wait progressively longer between retries, and add randomness to prevent all retrying clients from hammering the server simultaneously.
import random
import time
import requests
def call_with_retry(url, max_retries=5, base_delay=0.5):
"""
Calls a remote URL with exponential backoff and jitter.
base_delay: initial wait time in seconds
"""
for attempt in range(max_retries):
try:
# Timeout prevents indefinite blocking
response = requests.get(url, timeout=2.0)
response.raise_for_status()
return response.json()
except (requests.Timeout, requests.ConnectionError) as e:
if attempt == max_retries - 1:
raise # Re-raise on final attempt
# Exponential backoff: 0.5s, 1s, 2s, 4s...
sleep_time = base_delay * (2 ** attempt)
# Jitter: add up to 50% of sleep_time randomly
jitter = random.uniform(0, sleep_time * 0.5)
time.sleep(sleep_time + jitter)
print(f"Retry {attempt + 1} after {sleep_time + jitter:.2f}s")
This snippet demonstrates three ideas simultaneously: always set a timeout on external calls, use exponential backoff to avoid retry storms, and add jitter to desynchronize retries across multiple clients.
In an interview, when you propose a microservice that calls another service, interviewers expect you to address: What happens if the downstream service is slow? What happens if it is down? Will you retry, and how? Failing to raise these questions signals that you have not designed for real-world network conditions.
💡 Pro Tip: Pair retries with idempotency keys for any non-idempotent operation (writes, payments, state changes). An idempotency key is a unique client-generated token that lets the server recognize a duplicate request and return the original result rather than processing it again.
Pitfall 2: Confusing Latency and Bandwidth
Latency and bandwidth are both measures of network performance, but they describe completely different phenomena and require completely different solutions. Mixing them up leads to proposing the wrong architectural fix for a given bottleneck.
Latency is the time it takes for a single packet to travel from source to destination — think of it as the delay before anything starts arriving. Bandwidth is the maximum rate at which data can be transferred — think of it as the width of the pipe.
Latency analogy:
Source ----[100ms delay]----> Destination
(A narrow road with a long drive time)
Bandwidth analogy:
Source ====[10 Gbps pipe]===> Destination
(A wide highway with high throughput)
High bandwidth, high latency:
You can send a lot of data, but it takes a while to start arriving.
Example: Satellite internet — 600ms latency, 50 Mbps bandwidth
Low bandwidth, low latency:
Data arrives quickly, but not much of it at once.
Example: A slow 3G connection in a nearby cell tower
🎯 Key Principle: Adding more bandwidth does not reduce latency. If a user in Tokyo is waiting 150ms for a response from a server in New York, increasing the server's network card from 1 Gbps to 10 Gbps changes nothing. You must bring the data geographically closer — via a CDN or edge caching — to reduce latency.
Conversely, if your bottleneck is throughput — for example, you are streaming 4K video to millions of users — the answer is more bandwidth and more parallel connections, not reducing round trips.
| Symptom | Root Cause | Correct Solution |
|---|---|---|
| 📍 Slow first byte, fast download | High latency | CDN, edge caching, reduce round trips |
| 📍 Fast first byte, slow full load | Low bandwidth | Compression, larger pipes, chunked transfer |
| 📍 Slow large file uploads | Low upload bandwidth | Multipart upload, compression |
| 📍 Slow API response for small payloads | High latency | Colocate services, reduce hops |
⚠️ Common Mistake: In interviews, candidates sometimes propose "adding more bandwidth" to fix a sluggish API response time. If the API returns 2KB of JSON, bandwidth is irrelevant — 2KB transfers in under a millisecond on any modern connection. The real culprit is almost always latency: too many round trips, or the server is physically far from the user.
💡 Mental Model: Think of latency as the time to pull the first drop of water from a hose and bandwidth as the diameter of the hose. A longer hose (more distance) increases delay. A wider hose (more bandwidth) moves more water per second, but does not make that first drop arrive faster.
Pitfall 3: Building Chatty Interfaces
A chatty interface is one that requires many small, sequential network calls to accomplish a single logical operation. Each call incurs its own latency cost — even if each individual call takes only 10ms, thirty sequential calls accumulate 300ms of pure network overhead before any computation time is added.
This pattern is extremely common when developers map HTTP endpoints directly onto database rows (a symptom often caused by object-relational mappers generating N+1 queries) or when microservices are decomposed too finely.
Chatty pattern — fetching a user's dashboard:
Client --> GET /user/42 (10ms)
Client --> GET /orders?user=42 (10ms)
Client --> GET /recommendations (10ms)
Client --> GET /notifications (10ms)
Client --> GET /balance/42 (10ms)
-------
Total network time: ~50ms (ignoring processing)
Plus: 5x connection overhead, 5x TLS handshakes
Batched pattern — same dashboard:
Client --> POST /dashboard { userId: 42 } (12ms)
-------
Total network time: ~12ms
Server aggregates data internally (no extra network hops)
The solution is batching — combining multiple logical requests into a single network call. This can be implemented at different levels:
- 🔧 API-level batching: Design endpoints that return composite resources (e.g.,
/dashboardinstead of five separate endpoints). - 🔧 GraphQL: Lets clients specify exactly what data they need in a single query, eliminating over-fetching and chatty round trips.
- 🔧 Message batching: In event-driven systems, accumulate multiple events before flushing them to a message broker rather than sending one event at a time.
// Chatty: one network request per user ID
async function getUsersNaive(userIds) {
const results = [];
for (const id of userIds) {
// Each iteration = one HTTP round trip!
const user = await fetch(`/api/users/${id}`).then(r => r.json());
results.push(user);
}
return results;
}
// Batched: single network request for all IDs
async function getUsersBatched(userIds) {
// One HTTP call regardless of how many IDs
const response = await fetch('/api/users/batch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ids: userIds })
});
return response.json(); // Returns all users in one payload
}
// For 100 users:
// getUsersNaive: ~1000ms (100 x 10ms round trips)
// getUsersBatched: ~15ms (1 round trip + slightly larger payload)
💡 Real-World Example: Facebook's GraphQL was invented specifically to solve the chatty interface problem their mobile clients faced. Mobile apps were making dozens of REST calls to render a single screen, each with its own latency cost on a cellular connection. A single GraphQL query collapsed all of them into one round trip.
🧠 Mnemonic: Think of ordering food at a restaurant. A chatty API is like sending your waiter back to the kitchen for each ingredient one at a time. Batching is writing your full order on a ticket and handing it over once.
Pitfall 4: Ignoring the Fallacies of Distributed Computing
In 1994, Peter Deutsch at Sun Microsystems enumerated a set of false assumptions that engineers routinely make about distributed networks. These became known as the Fallacies of Distributed Computing, and they remain as relevant today as they were thirty years ago. Every flawed distributed system design can be traced back to violating at least one of them.
The eight fallacies are:
- The network is reliable. (Already covered — it isn't.)
- Latency is zero. (It isn't — and it varies unpredictably.)
- Bandwidth is infinite. (It isn't — and it is shared.)
- The network is secure. (It isn't — always assume adversarial conditions.)
- Topology doesn't change. (It does — servers go down, IPs change, routes shift.)
- There is one administrator. (There isn't — especially across org boundaries.)
- Transport cost is zero. (It isn't — serialization, compression, and egress have real costs.)
- The network is homogeneous. (It isn't — different clients, protocols, and hardware coexist.)
In system design interviews, fallacies 1–3 and 5–7 appear most frequently. Let's focus on the ones interviewers probe hardest.
Fallacy 5 — Topology doesn't change trips up candidates who design systems assuming that service discovery is static. In production, instances are constantly being added, removed, and replaced. A design that hardcodes IP addresses or relies on DNS TTLs being honored will break in real deployments. The correct response is to use a service registry (like Consul or etcd) and implement health checks so clients only route to live instances.
Fallacy 7 — Transport cost is zero leads engineers to underestimate serialization overhead and data egress costs. Sending 10,000 small JSON objects across a network is dramatically more expensive than sending one compressed batch, both in CPU time (serialization/deserialization) and in network bytes. This connects directly to the chatty interface pitfall — but it also has real financial consequences in cloud environments where data egress (traffic leaving a data center) is billed per gigabyte.
Fallacy 7 in action — Cross-region data transfer costs:
[Service A - US-East] -------> [Service B - EU-West]
1 million API calls/day @ 2KB each
= ~2 GB/day outbound
= ~60 GB/month
AWS egress price: ~$0.09/GB
= ~$5.40/month just in egress fees
With batching + compression (10:1 ratio):
= ~6 GB/month
= ~$0.54/month
Savings: ~90%
⚠️ Common Mistake: Candidates who design multi-region architectures sometimes forget to account for data transfer costs between regions and availability zones. In an interview, briefly acknowledging this demonstrates production awareness that separates strong candidates from good ones.
🤔 Did you know? "Bandwidth is infinite" is violated in surprising ways even inside a single data center. In a cloud environment, network bandwidth is shared among virtual machines on the same physical host. A noisy neighbor VM can throttle your throughput without any change to your own code or configuration.
Pitfall 5: Ignoring Network Topology in Performance Estimates
When interviewers ask you to estimate the performance of a system — a classic back-of-the-envelope exercise — candidates often calculate compute throughput and storage capacity carefully while completely ignoring network topology: where services are physically located relative to each other and to users.
Network topology refers to the arrangement of nodes, links, and routing paths in a network. In system design, the relevant questions are: Are your services in the same data center? The same region? Different continents? Are users hitting your servers directly or through a CDN edge node? Each configuration has radically different latency profiles.
Typical latency reference values (approximate):
Same process (in-memory call): ~0.001ms
Same machine (loopback): ~0.05ms
Same rack (LAN): ~0.1–0.5ms
Same data center (cross-rack): ~1–5ms
Same region (cross-AZ): ~2–10ms
Cross-region (US East -> US West): ~60–80ms
Cross-continent (US -> Europe): ~100–150ms
Cross-continent (US -> Asia): ~150–200ms
Satellite (low earth orbit, e.g. Starlink): ~20–40ms
Satellite (geostationary): ~500–700ms
These numbers matter enormously when reasoning about system behavior. A design that requires three synchronous service calls between US-East and EU-West will incur at minimum 3 × 120ms = 360ms of pure network latency, before any processing time. That single architectural choice determines whether an interactive user request feels snappy or sluggish.
🎯 Key Principle: When estimating system latency in an interview, always state your topology assumptions explicitly. Say: "I'm assuming both services are in the same region, so cross-service calls add roughly 2–5ms each." This signals awareness that topology is a variable, not a constant.
Cross-AZ Versus Cross-Region Tradeoffs
A subtlety that separates strong candidates is understanding the difference between Availability Zones (AZs) and regions. Most cloud providers split regions into multiple physically separate data centers (AZs) within the same metropolitan area. Cross-AZ latency is low (2–10ms) but not zero, and it has associated bandwidth costs. Cross-region latency is significantly higher.
AWS us-east-1 region topology:
Region: us-east-1 (N. Virginia)
├── AZ: us-east-1a [Data Center A] ─┐
├── AZ: us-east-1b [Data Center B] ├── ~2-5ms between AZs
├── AZ: us-east-1c [Data Center C] ─┘
└── AZ: us-east-1d [Data Center D]
Deploy database primary in 1a,
read replica in 1b:
→ Replication lag is predictably low (~2ms)
→ Failover within region is fast
Compare to cross-region replica in eu-west-1:
→ Replication lag can be 100-200ms
→ User reads from EU replica still benefit
(if users are in EU)
💡 Real-World Example: A common interview question involves designing a globally distributed database. Candidates who account for topology will note that a synchronous write to replicas in three regions adds 150–300ms to every write operation (the replication lag imposed by the speed of light across continents). This is why most globally distributed databases — Cassandra, DynamoDB Global Tables, Spanner — use eventual consistency for cross-region replication: the physics of latency make strict synchronous replication impractical at global scale.
How to Apply This in Interviews
When you sketch a system design and begin estimating performance, build the habit of annotating your diagram with topology labels:
- 📍 Mark which services are colocated (same AZ, same host)
- 📍 Mark which calls cross AZ or region boundaries
- 📍 Estimate the latency budget for each remote call
- 📍 Count the total number of serial hops in your critical path
If your critical path has five serial cross-region calls, your p99 latency will be dominated by network time regardless of how fast your servers are. Recognizing this allows you to propose meaningful optimizations: collapsing serial calls into parallel ones, caching intermediate results, or pushing computation closer to the user.
📋 Quick Reference Card: Networking Pitfalls Cheat Sheet
| ⚠️ Pitfall | ❌ Wrong Thinking | ✅ Correct Thinking |
|---|---|---|
| 🔴 Unreliable network | "The call will either work or fail clearly" | "The call may partially fail; I need timeouts, retries, and idempotency" |
| 🔴 Latency vs bandwidth | "Add bandwidth to speed up API responses" | "Small payloads are latency-bound; reduce round trips or move data closer" |
| 🔴 Chatty interfaces | "One endpoint per resource is clean design" | "Batch related data; minimize round trips on the critical path" |
| 🔴 Distributed fallacies | "My services will always be reachable" | "Plan for topology changes, transport costs, and partial failures" |
| 🔴 Ignoring topology | "Latency is roughly the same everywhere" | "Cross-region calls add 100–200ms; always state and account for topology" |
Bringing It All Together
These five pitfalls are interconnected. Ignoring network reliability leads to missing timeouts and retries. Confusing latency and bandwidth leads to proposing the wrong fix. Building chatty interfaces amplifies latency costs. Violating the fallacies of distributed computing produces designs that look correct on a whiteboard but fail in production. And ignoring topology means your performance estimates are fiction.
The common thread is this: the network is not a free, instant, reliable bus. It is a physical medium with real costs, real delays, and real failure modes. Engineers who internalize this truth design systems that survive contact with reality. Those who do not build systems that work perfectly in demos and fail mysteriously in production.
In a system design interview, you do not need to enumerate every possible failure mode — that would take days. But you should consistently signal that you know these issues exist by sprinkling in phrases like: "I'd add a timeout here," "We should batch these calls to avoid chatty behavior," "Cross-region replication will add latency, so I'd use eventual consistency for these reads." Each acknowledgment earns credibility with your interviewer and moves the conversation from theoretical to production-ready.
Key Takeaways and Preparing for What Comes Next
You have covered a significant amount of ground in this lesson. Before the first section, networking was likely something you understood well enough to build applications but not necessarily well enough to reason about at a systems level — the level that matters in a system design interview. That gap has now closed considerably. You can describe the layers of the OSI model and explain why they exist. You can trace how a packet moves from an application process through the operating system, across a physical network, and into a remote process. You understand why latency is the silent killer of distributed system performance, and you know the most common traps engineers fall into when they stop thinking carefully about the network layer.
This final section does three things: it consolidates everything you have learned into reference-ready formats you can return to quickly before an interview, it builds a bridge toward the next three sub-topics in this series, and it gives you a concrete practice exercise that will force you to synthesize every concept from this lesson into a single coherent narrative.
The OSI and TCP/IP Models: A Final Mental Model
The single most important thing to internalize about layered network models is not the names of the layers — it is the why behind the layering. Each layer exists so that the layers above it do not need to care about the complexity below it. HTTP does not need to know that TCP retransmits lost segments. TCP does not need to know that Ethernet switches forward frames based on MAC addresses. This separation of concerns is what makes the internet composable and replaceable at each tier.
🎯 Key Principle: In a system design interview, you invoke the OSI model not to recite it, but to locate a problem. When a senior interviewer asks "why is this design slow?", the ability to say "I think the bottleneck lives at Layer 4 — we're exhausting TCP connections before data volume is even the issue" immediately signals that you think in layers.
Here is the consolidated view you should be able to reproduce from memory:
┌─────────────────────────────────────────────────────────────┐
│ OSI MODEL vs TCP/IP MODEL │
├───────────────────────┬─────────────────────────────────────┤
│ OSI Layers │ TCP/IP Equivalent │
├───────────────────────┼─────────────────────────────────────┤
│ 7. Application │ │
│ 6. Presentation │ Application Layer │
│ 5. Session │ (HTTP, DNS, SMTP, FTP, WebSocket) │
├───────────────────────┼─────────────────────────────────────┤
│ 4. Transport │ Transport Layer │
│ │ (TCP, UDP) │
├───────────────────────┼─────────────────────────────────────┤
│ 3. Network │ Internet Layer │
│ │ (IP, ICMP, routing) │
├───────────────────────┼─────────────────────────────────────┤
│ 2. Data Link │ Network Access Layer │
│ 1. Physical │ (Ethernet, Wi-Fi, MAC addressing) │
└───────────────────────┴─────────────────────────────────────┘
💡 Mental Model: Think of the TCP/IP model as the "working engineer's shorthand" and the OSI model as the "academic blueprint." In interviews, use OSI layer numbers to pinpoint problems precisely. In implementation, think in TCP/IP terms because that is what your operating system and libraries actually expose.
🧠 Mnemonic: To remember OSI layers top-to-bottom: "All People Seem To Need Data Processing" — Application, Presentation, Session, Transport, Network, Data Link, Physical.
IP Addresses, Ports, and Sockets: The Foundation Everything Else Stands On
Every protocol you will study in the upcoming lessons — HTTP, DNS, TCP, UDP — is ultimately carried by the IP-port-socket model. There is no HTTP request that does not resolve to an IP address and arrive on a port. There is no DNS response that does not travel as a UDP datagram from a source socket to a destination socket. Keeping this mental anchor in place will make every higher-level protocol feel familiar rather than abstract.
The key relationships to keep sharp:
🔧 IP address — identifies a machine (or more precisely, a network interface) on the internet. IPv4 gives you ~4.3 billion addresses; IPv6 gives you effectively unlimited addresses. In system design, IP addresses are the coordinates of your servers.
🔧 Port — identifies a process or service on a machine. The OS multiplexes all incoming IP traffic to the correct process based on the destination port. Well-known ports (80 for HTTP, 443 for HTTPS, 53 for DNS) are conventions, not laws.
🔧 Socket — a bidirectional communication endpoint identified by the four-tuple (source IP, source port, destination IP, destination port). Two connections between the same client and server can coexist because they have different source ports, making the four-tuple unique.
Here is a minimal Python example that makes the socket model tangible. This server accepts a single TCP connection and echoes back whatever it receives — a pattern that underlies every HTTP server ever written:
import socket
## AF_INET = IPv4, SOCK_STREAM = TCP (reliable, ordered, connection-oriented)
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
## SO_REUSEADDR lets us restart the server quickly without waiting
## for the OS to release the port from a TIME_WAIT state
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
## Bind to all interfaces on port 9000
server_socket.bind(('0.0.0.0', 9000))
## Allow up to 5 queued connections (the backlog)
server_socket.listen(5)
print("Listening on port 9000...")
## accept() blocks until a client connects.
## It returns a NEW socket for this specific connection
## (the original server_socket keeps listening for new clients)
conn, client_address = server_socket.accept()
print(f"Connection from {client_address}")
data = conn.recv(1024) # Read up to 1024 bytes
print(f"Received: {data.decode()}")
## Echo it back
conn.sendall(data)
conn.close()
server_socket.close()
Notice the setsockopt(SO_REUSEADDR) call. This is exactly the kind of detail that separates engineers who have reasoned about networking from those who have only used high-level frameworks. In production, failing to set this option means your server cannot restart quickly after a crash — a real operational problem.
💡 Pro Tip: When a system design interviewer asks "how does your API server handle 100,000 simultaneous users?", the socket model is your starting point. Each connection is a socket. The OS can hold only so many file descriptors. This is why stateless servers, connection pools, and load balancers exist — all of which you will design in future lessons.
Latency Numbers You Must Know Cold
Memorizing latency numbers is one of the highest-return investments you can make before a system design interview. These numbers give you the ability to make quantified architectural claims rather than vague ones. The difference between saying "disk is slower than memory" and "disk random access is roughly 100,000× slower than an L1 cache read" is the difference between a junior and a senior answer.
📋 Quick Reference Card: Latency Numbers Every Engineer Should Know
| ⏱️ Operation | 📊 Approximate Latency | 🔄 Relative to L1 Cache |
|---|---|---|
| 🔵 L1 cache reference | 0.5 ns | 1× |
| 🔵 L2 cache reference | 7 ns | 14× |
| 🟢 Main memory (RAM) access | 100 ns | 200× |
| 🟡 SSD random read | 150 µs | 300,000× |
| 🟠 HDD random seek | 10 ms | 20,000,000× |
| 🟠 Round-trip within same datacenter | 0.5 ms | 1,000,000× |
| 🔴 Round-trip across US coasts | ~40 ms | 80,000,000× |
| 🔴 Round-trip US ↔ Europe | ~80 ms | 160,000,000× |
| 🔴 Round-trip US ↔ Australia | ~150 ms | 300,000,000× |
| 🟣 Read 1 MB sequentially from SSD | ~1 ms | — |
| 🟣 Read 1 MB sequentially from network | ~10 ms | — |
⚠️ Critical Point to Remember: These numbers are order-of-magnitude estimates, not precise measurements. Their value is in the ratios, not the exact figures. An interviewer will never penalize you for saying "roughly 50ms" instead of "exactly 40ms" — but they will penalize you for thinking cross-continental latency is negligible.
🤔 Did you know? The speed of light in fiber optic cable is approximately 200,000 km/s — about 67% of the speed of light in a vacuum. This physical constraint means a New York to London round-trip has an absolute minimum latency of roughly 56ms even with perfect hardware. Real-world routing and processing pushes that to 70-80ms.
The following Python snippet lets you measure real round-trip latency to any host, which is a useful exercise for building intuition:
import socket
import time
def measure_tcp_rtt(host: str, port: int = 80, samples: int = 5) -> dict:
"""
Measure TCP connection round-trip time to a host.
This captures the SYN / SYN-ACK / ACK handshake time,
which is a real lower-bound on HTTP request latency.
"""
latencies = []
for i in range(samples):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5) # Fail fast if host is unreachable
start = time.perf_counter() # High-resolution timer
try:
sock.connect((host, port))
elapsed_ms = (time.perf_counter() - start) * 1000
latencies.append(elapsed_ms)
except socket.error as e:
print(f"Sample {i+1} failed: {e}")
finally:
sock.close()
if not latencies:
return {"error": "All samples failed"}
return {
"host": host,
"port": port,
"samples": len(latencies),
"min_ms": round(min(latencies), 2),
"max_ms": round(max(latencies), 2),
"avg_ms": round(sum(latencies) / len(latencies), 2)
}
## Try measuring RTT to a well-known host
result = measure_tcp_rtt("example.com", port=80)
print(result)
## Example output: {'host': 'example.com', 'port': 80,
## 'samples': 5, 'min_ms': 11.2,
## 'max_ms': 14.8, 'avg_ms': 12.6}
Run this against servers in different geographic regions. The results will permanently calibrate your intuition for what "fast" means across a network.
How the Upcoming Lessons Build Directly on These Foundations
This lesson was intentionally foundational. Every topic that follows in the system design series is an application of what you have just learned. Understanding how they connect will help you study them faster and remember them longer.
HTTP and REST APIs
HTTP is an application-layer protocol that runs on top of TCP. Every HTTP request is a TCP connection (or a reused one, in HTTP/1.1 keep-alive and HTTP/2) between a client socket and a server socket. When you learn about HTTP methods, headers, status codes, and REST conventions in the next lesson, you will be learning the language that two processes speak once the socket connection you learned about today has been established.
The latency concepts from this lesson apply directly: HTTP/2 was designed specifically to reduce the impact of round-trip latency by multiplexing multiple requests over a single TCP connection, eliminating the per-request handshake cost. HTTP/3 goes further by replacing TCP with QUIC (built on UDP) to eliminate head-of-line blocking. You will understand all of this immediately because you already understand TCP's connection overhead and what a round-trip costs.
DNS and CDN
DNS (Domain Name System) is how a human-readable hostname like api.myservice.com becomes an IP address that can be used in a socket's four-tuple. Without DNS, every request would require you to know the raw IP address of the server — clearly impractical at scale. DNS itself is primarily a UDP-based application-layer protocol (port 53), which means the latency and packet-loss tradeoffs of UDP that you learned about apply directly to every domain name resolution in your system.
CDNs (Content Delivery Networks) are a direct architectural response to the geographic latency numbers in this lesson's reference table. Because a round-trip to a server on the other side of the world costs 150ms or more, CDNs place servers geographically close to users so that the physics of latency work for the architecture instead of against it. The IP addressing model — specifically, techniques like Anycast routing where a single IP address routes to the nearest physical server — is what makes CDNs technically possible.
TCP vs UDP
This lesson introduced TCP and UDP at a conceptual level. The upcoming dedicated lesson will go deep on the tradeoffs: TCP's reliability guarantees (retransmission, ordering, flow control, congestion control) versus UDP's low-overhead, fire-and-forget model. The performance metrics you learned here — latency, bandwidth, throughput — are precisely the axes along which TCP and UDP differ. Real-time applications like video streaming, gaming, and VoIP choose UDP specifically to avoid the latency cost of TCP's retransmission mechanism, accepting occasional packet loss in exchange for lower and more consistent latency. You are now perfectly positioned to reason about those tradeoffs quantitatively.
Practice Exercise: Trace a Single Web Request End to End
The best way to solidify everything in this lesson is to narrate, in writing or out loud, the complete journey of a single web request using the vocabulary and concepts you have learned. This is also an excellent interview preparation technique — interviewers frequently ask "walk me through what happens when you type a URL into a browser" precisely because it exercises the full stack of networking knowledge.
Here is the exercise framework. For the URL https://www.example.com/products?id=42, trace each of the following stages and identify which OSI layer, which protocol, and which performance considerations are relevant at each step:
STAGE 1: DNS Resolution
└─ What layer? What protocol? What port?
└─ Where does latency come from here?
└─ What is cached, and where?
STAGE 2: TCP Handshake (+ TLS Handshake for HTTPS)
└─ What is a socket four-tuple for this connection?
└─ How many round trips does the handshake cost?
└─ What does this cost in ms for a US-to-Europe request?
STAGE 3: HTTP Request Transmission
└─ What application-layer protocol is used?
└─ How does the request get broken into TCP segments?
└─ What happens if a segment is lost in transit?
STAGE 4: Server Processing
└─ Which port does the server process listen on?
└─ How does the OS route the packet to the right process?
STAGE 5: HTTP Response and Connection Teardown
└─ How does the response travel back?
└─ What is the total round-trip latency budget?
└─ How would a CDN change this picture?
💡 Real-World Example: At Google, new engineers often go through an exercise called "What happens when you Google something?" that traces a search request through DNS, TCP, TLS, HTTP/2, load balancers, caching layers, and back — sometimes producing answers that are tens of thousands of words long. Your version does not need to be that deep, but the discipline of tracing end-to-end is the same skill.
Recap: What You Now Understand That You Didn't Before
Let's be explicit about the knowledge delta this lesson created:
📋 Quick Reference Card: Networking Fundamentals Summary
| 📘 Concept | 🎯 What You Can Now Do | 🔗 Where It Shows Up Next |
|---|---|---|
| 🧠 OSI/TCP-IP Layers | Locate any protocol in the stack; diagnose problems by layer | HTTP (L7), TCP/UDP (L4), IP routing (L3) |
| 🌐 IP Addressing | Distinguish public vs private IPs; explain IPv4 exhaustion | DNS lesson, load balancer design |
| 🔌 Ports & Sockets | Explain how 100k connections work; reason about fd limits | HTTP keep-alive, connection pooling |
| ⏱️ Latency Numbers | Make quantified architectural claims in interviews | Every performance discussion |
| 📶 Bandwidth vs Throughput | Diagnose whether a system is latency-bound or bandwidth-bound | CDN design, data pipeline design |
| ⚠️ Networking Pitfalls | Avoid the mistakes that sink system design interview answers | Every distributed system lesson |
The Three Most Important Points to Carry Forward
🎯 The network is not free. Every call across a network boundary has a latency cost measured in milliseconds. At scale, milliseconds multiply into seconds. Design systems to minimize network hops and to place data close to where it is needed.
🎯 Layers are your diagnostic tool. When a system misbehaves, walking the OSI model from the bottom up is a systematic way to isolate the problem. Is it physical? Is it routing? Is it the transport layer? Is it the application protocol? This framing is immediately recognizable to experienced interviewers as evidence of structured thinking.
🎯 The socket four-tuple is the foundation. Every high-level abstraction — HTTP sessions, WebSocket connections, gRPC streams — is ultimately a socket. When you understand sockets, you understand the resource model that underlies all of it: connections cost memory and file descriptors, handshakes cost round-trips, and keeping connections alive trades memory for latency.
⚠️ Final Critical Point: In a system design interview, the biggest networking mistake is not a wrong answer — it is an unquantified answer. "Memory is faster than disk" is a junior observation. "Memory access takes ~100ns while SSD random read takes ~150µs — 1500× slower — which is why we cache hot data in Redis instead of re-reading from Postgres" is a senior answer. The latency table in this lesson is your quantification toolkit. Use it.
Practical Next Steps
📚 Before the next lesson: Run the TCP RTT measurement script from this lesson against a server in your own region and one in a distant region. Record the numbers. You now have a personal calibration baseline that you can reference when reasoning about latency in design discussions.
🔧 Deepen your intuition: Use traceroute (or tracert on Windows) to trace the network path between your machine and a remote server. Count the hops. Observe where latency jumps significantly — those are usually the long-haul fiber links crossing oceans or continents. This makes the latency table viscerally real.
🎯 Prepare your narrative: Practice answering "walk me through what happens when a browser makes an HTTPS request" out loud, using the layer model and latency numbers from this lesson as your scaffold. Time yourself. A clear, structured, quantified answer that takes 3-4 minutes is exactly what a senior interviewer wants to hear. The upcoming HTTP and DNS lessons will add more precision to this narrative — but the skeleton you have now is already stronger than what most candidates walk into interviews with.
You have built the foundation. Everything that follows is a structure built on top of it.