CS Prep Hub

Computer Networks

A zero-to-hero reference for Computer Networks interviews — from the OSI model to TCP internals, congestion control, HTTP/DNS/security, and the advanced trivia interviewers love to ask. Every section has worked examples, comparison tables, and collapsible Q&A you can use for rapid-fire revision.

1. Networking Basics

Network Types Basic

Networks are classified by the geographical area they cover:

  • PAN (Personal Area Network) — covers a few meters; Bluetooth, USB, a phone tethered to a laptop.
  • LAN (Local Area Network) — a single building or campus; an office Wi-Fi/Ethernet network. High speed, low latency, usually owned by one organization.
  • MAN (Metropolitan Area Network) — spans a city; cable TV networks, city-wide fiber backbones.
  • WAN (Wide Area Network) — spans countries/continents; the internet itself is the largest WAN, built by interconnecting many smaller networks.

Network Topologies Basic

  • Bus — all devices share a single backbone cable. Cheap, but one break kills the whole segment and collisions are common.
  • Star — every device connects to a central hub/switch. If a cable fails only that device drops; if the center fails, everything drops. Most common in modern LANs.
  • Ring — each device connects to exactly two neighbors, forming a loop; data travels in one (or both, dual-ring) direction. A single break can down the ring unless it's dual-ring.
  • Mesh — every device connects to every other device (full mesh) or to several others (partial mesh). Extremely fault tolerant and used in backbone/WAN links, but expensive to wire (full mesh needs n(n-1)/2 links).
  • Hybrid — a real-world combination, e.g. a star-of-stars connected by a backbone (tree topology).

The OSI 7-Layer Model Basic

The OSI (Open Systems Interconnection) model is a conceptual, vendor-neutral framework that splits network communication into seven layers. Each layer talks to its peer layer on the other end and only interacts directly with the layer immediately above/below it. Mnemonic (top to bottom): "All People Seem To Need Data Processing".

#LayerPDU (Protocol Data Unit)ResponsibilityExamples
7ApplicationData / MessageUser-facing protocols; the interface between network and app software.HTTP, FTP, SMTP, DNS
6PresentationDataTranslation, encryption, compression — ensures data is in a usable format.TLS/SSL, JPEG, ASCII/EBCDIC
5SessionDataEstablishes, manages, and terminates sessions/dialogs between hosts.NetBIOS, RPC, sockets sessions
4TransportSegment (TCP) / Datagram (UDP)End-to-end delivery, reliability, flow & congestion control, multiplexing via ports.TCP, UDP
3NetworkPacketLogical addressing and routing across networks.IP, ICMP, routers
2Data LinkFrameNode-to-node delivery on the same physical link; MAC addressing, error detection.Ethernet, Wi-Fi (802.11), switches
1PhysicalBitRaw bit transmission over the physical medium (voltage, light, radio).Cables, hubs, radio signals

The TCP/IP 4-Layer Model Basic

The TCP/IP (or "internet") model is what the actual internet runs on. It's less academic and more practical than OSI, collapsing the top three OSI layers into one:

  1. Application — combines OSI's Application, Presentation, and Session layers. (HTTP, DNS, FTP, SMTP, TLS)
  2. Transport — same as OSI layer 4. (TCP, UDP)
  3. Internet — same as OSI layer 3. (IP, ICMP, ARP is sometimes placed here too)
  4. Network Access / Link — combines OSI's Data Link and Physical layers. (Ethernet, Wi-Fi)

OSI vs TCP/IP Intermediate

AspectOSI ModelTCP/IP Model
Layers74 (sometimes drawn as 5, splitting link/physical)
NatureTheoretical, protocol-independent reference modelPractical, describes protocols actually in use
Developed byISODARPA / the internet's original architects
ApproachLayer first, then protocols were fitted to itProtocols existed first, model described them after
ReliabilityGuarantees delivery at the layer definition level (conceptually)Reliability depends on chosen transport protocol (TCP reliable, UDP not)

Encapsulation & Decapsulation Intermediate

Encapsulation happens as data moves down the stack on the sender: each layer wraps the data from the layer above with its own header (and sometimes trailer).

  • Application creates data (e.g. an HTTP request).
  • Transport wraps it into a segment (adds TCP/UDP header with port numbers, sequence numbers).
  • Network wraps it into a packet (adds IP header with source/destination IP).
  • Data Link wraps it into a frame (adds MAC header + trailer with CRC for error checking).
  • Physical converts the frame into raw bits for transmission.

Decapsulation is the reverse — as the frame arrives at the receiver, each layer strips off its own header and passes the remaining payload up to the layer above, until the original application data is recovered.

💡 Interview Tip

If asked "what happens when you type a URL into a browser and press enter", walk through DNS resolution, TCP handshake, TLS handshake, HTTP request/response, and rendering — that question is really testing your grasp of encapsulation and the full stack end to end.

Q: Why do we need both OSI and TCP/IP models if the internet only uses TCP/IP?

OSI is a teaching/reference tool — it gives precise vocabulary ("layer 3 problem", "layer 7 firewall") and cleanly separates concerns even though no real stack implements all 7 layers as distinct protocols. TCP/IP is what's actually deployed. Interviewers use OSI to test whether you can categorize a technology (e.g. "which layer does a switch operate at?") even though your code targets TCP/IP.

Q: At which OSI layer does a switch operate? A router? A hub?

A hub is Layer 1 (Physical) — it just repeats electrical signals to all ports with no addressing awareness. A switch is Layer 2 (Data Link) — it forwards frames based on MAC addresses. A router is Layer 3 (Network) — it forwards packets based on IP addresses. Modern "Layer 3 switches" blur this by doing IP routing in hardware.

Q: What is the PDU at each layer, and why does the term change?

Bit (Physical) → Frame (Data Link) → Packet (Network) → Segment/Datagram (Transport) → Data/Message (Application). The name changes because each layer adds its own header, changing the unit's structure and meaning — a "packet" becomes a "frame" the moment MAC addressing is added around it.

Q: Give a real-world analogy for encapsulation.

Mailing a letter: you write a letter (data), put it in an envelope with the recipient's name (transport header), put that envelope in a shipping box addressed with a street address (network header), and the courier company puts a routing label on the box for their internal sorting (link header). Each layer only reads its own label to do its job.

Q: What's the difference between a bus and a star topology in terms of fault tolerance?

In bus topology, a single cable break can partition or kill the entire segment because all nodes share one physical medium. In star topology, each node has its own dedicated link to the central device, so a cable fault only isolates that one node — but the central hub/switch becomes a single point of failure for the whole network.

3. Network Layer

IP Addressing: Classful vs Classless (CIDR) Intermediate

Classful addressing (obsolete) divided the entire IPv4 space into rigid classes based on the leading bits:

ClassLeading bitsRangeDefault maskHosts/network
A01.0.0.0 – 126.255.255.255/8~16.7 million
B10128.0.0.0 – 191.255.255.255/16~65,000
C110192.0.0.0 – 223.255.255.255/24254
D1110224.0.0.0 – 239.255.255.255Multicast, not host addressing
E1111240.0.0.0 – 255.255.255.255Reserved/experimental

This wasted huge numbers of addresses (a company needing 300 hosts had to take a full Class B, wasting ~65,000 addresses). CIDR (Classless Inter-Domain Routing) replaced it: the network/host split is expressed as a /n prefix length that can be any value (not just /8, /16, /24), allowing addresses to be allocated in right-sized blocks and letting routers aggregate many small networks into one advertised "supernet" route.

Subnetting — Worked Example Intermediate

Task: split 192.168.1.0/24 into 4 equal subnets.

Subnetting Walkthrough
Original network: 192.168.1.0/24  (256 addresses, mask 255.255.255.0)
Need 4 subnets → need 2 extra bits (2^2 = 4) borrowed from the host portion.
New prefix length = /24 + 2 = /26   → mask 255.255.255.192

Block size = 2^(32-26) = 2^6 = 64 addresses per subnet

Subnet 1: 192.168.1.0/26    usable hosts 192.168.1.1   - 192.168.1.62   (broadcast .63)
Subnet 2: 192.168.1.64/26   usable hosts 192.168.1.65  - 192.168.1.126  (broadcast .127)
Subnet 3: 192.168.1.128/26  usable hosts 192.168.1.129 - 192.168.1.190  (broadcast .191)
Subnet 4: 192.168.1.192/26  usable hosts 192.168.1.193 - 192.168.1.254  (broadcast .255)

Each subnet: 64 total addresses - 2 (network + broadcast) = 62 usable host addresses
💡 Interview Tip

Quick formula to memorize: usable hosts per subnet = 2^(32 - prefix length) − 2. The "−2" removes the network address (all host bits 0) and the broadcast address (all host bits 1). For CIDR summarization the same math runs in reverse: combine contiguous small networks into one shorter prefix.

IPv4 vs IPv6 Intermediate

IPv4's 32-bit address space (~4.3 billion addresses) has been effectively exhausted given the number of internet-connected devices — IPv6 was designed with a 128-bit address space (~340 undecillion addresses) to solve this permanently, plus other structural improvements.

AspectIPv4IPv6
Address length32-bit (e.g. 192.168.1.1)128-bit (e.g. 2001:0db8::1)
Header sizeVariable (20-60 bytes, options field)Fixed 40 bytes, extension headers instead of options
Header checksumPresent (recalculated at every hop, costly)Removed — relies on link/transport layer checksums
FragmentationDone by routers and the senderOnly by the sending host (Path MTU Discovery); routers never fragment
Address configurationManual or DHCPDHCPv6 or Stateless Address Autoconfiguration (SLAAC)
BroadcastYesNo — replaced by multicast/anycast
Built-in securityOptional (IPsec bolted on)IPsec support was originally mandated in the spec
NAT needWidely required due to address scarcityNot needed — every device can have a globally unique address

Routing Algorithms Advanced

  • Distance Vector — each router shares its entire routing table (destination + distance/cost) with its directly connected neighbors periodically. Routers build their table via the Bellman-Ford idea: "if my neighbor can reach X in N hops, I can reach X in N+1 hops via that neighbor." Simple but slow to converge, and prone to the "count-to-infinity" problem after a link fails (mitigated with split horizon, route poisoning, hold-down timers).
  • Link State — each router discovers its directly connected links/costs, floods that information to every router in the network (not just neighbors), so every router builds an identical full topology map. Each router then independently runs Dijkstra's shortest-path algorithm on that map. Converges faster and more accurately than distance vector, at the cost of more memory/CPU and more control traffic during flooding.

RIP vs OSPF vs BGP Advanced

ProtocolTypeMetricScopeNotes
RIPDistance Vector (IGP)Hop count (max 15, 16 = unreachable)Interior (within one AS)Simple, old, slow convergence — mostly replaced by OSPF in practice.
OSPFLink State (IGP)Cost (based on bandwidth)Interior (within one AS)Fast convergence, supports hierarchical "areas" to scale, widely used inside enterprise/ISP networks.
BGPPath Vector (EGP)Policy-based (AS-path length, local preference, etc., not just a simple metric)Exterior (between Autonomous Systems)The routing protocol that holds the entire internet together — routes between ISPs/organizations based on policy, not just shortest path.

IGP (Interior Gateway Protocol, e.g. RIP/OSPF) routes within a single autonomous system (a network under one administrative control, like one ISP or company). EGP (Exterior Gateway Protocol, e.g. BGP) routes between autonomous systems.

NAT (Network Address Translation) Intermediate

NAT lets many devices on a private network share a single public IP address, translating private (RFC 1918: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) addresses to a public one at the network's edge (typically the router).

  • Static NAT — one private IP is permanently mapped to one public IP, 1:1.
  • Dynamic NAT — private IPs are mapped to any available public IP from a pool, on demand.
  • PAT (Port Address Translation), also called NAT overload — the common home-router case: many private IPs share one public IP, disambiguated by different source port numbers. The router keeps a translation table of (private IP, private port) ↔ (public IP, public port) so return traffic gets routed back to the right internal device.

ICMP, Ping & Traceroute Intermediate

ICMP (Internet Control Message Protocol) is a Network-layer protocol used for diagnostics and error reporting (not for carrying application data) — e.g. "Destination Unreachable", "Time Exceeded", "Echo Request/Reply".

Ping sends an ICMP Echo Request to a target and waits for an ICMP Echo Reply, measuring round-trip time and packet loss — the simplest reachability test.

Traceroute discovers the path (each router hop) to a destination by exploiting the IP TTL (Time To Live) field: it sends packets with TTL=1, 2, 3, … Each router that receives a packet decrements the TTL; if it hits 0, the router discards it and sends back an ICMP "Time Exceeded" message, revealing its own IP address. By incrementing TTL one at a time, traceroute maps the router at every hop until the destination is reached.

IP Fragmentation Advanced

Every link has a MTU (Maximum Transmission Unit) — the largest frame it can carry (Ethernet's is typically 1500 bytes). If an IP packet is larger than the MTU of a link it must cross, it gets fragmented into smaller pieces, each with its own IP header, an identification field shared across all fragments of the original packet, a fragment offset marking its position in the original data, and a More Fragments flag (set on all but the last fragment). The receiver reassembles fragments using these fields. In IPv4, both routers and the sending host can fragment; in IPv6, only the sending host can (via Path MTU Discovery, which probes the smallest MTU along the path up front, using ICMP "Packet Too Big" messages).

⚠️ Common Pitfall

Fragmentation is expensive: if any one fragment is lost, the entire original packet must be retransmitted (there's no per-fragment retransmission at IP level). This is a major reason IPv6 removed in-network fragmentation entirely and pushes MTU discovery to the sender.

Q: Why was CIDR introduced, and what problem did it actually solve?

Classful addressing only offered fixed-size blocks (/8, /16, /24), so an organization needing, say, 500 addresses had to take an entire Class B (65,536 addresses), wasting the rest — this rapidly exhausted the IPv4 address space and bloated routing tables. CIDR allows arbitrary-length prefixes so blocks can be sized to actual need, and lets ISPs aggregate ("summarize") many customer routes into one shorter-prefix advertisement, shrinking global routing tables.

Q: Why does distance vector routing suffer from "count to infinity", and how is it fixed?

If a link fails, routers only know costs from their immediate neighbors, not the full topology — so if router A's route to X was via B, and B's route was via A (a loop, unknown to either), they can keep incrementing each other's advertised cost indefinitely instead of realizing X is unreachable. Fixes include split horizon (never advertise a route back to the neighbor you learned it from), route poisoning (advertise a failed route with infinite cost), and hold-down timers (ignore new info about a route for a period after it's marked down).

Q: How does PAT allow an entire household to share one public IP?

The router rewrites the source IP of outgoing packets to its own public IP, and rewrites the source port to a unique port it picks from its own pool, recording the mapping (private IP:port ↔ public IP:port) in a NAT table. Since TCP/UDP allow up to ~65,000 ports, a single public IP can multiplex tens of thousands of simultaneous internal connections. Return traffic is matched against the NAT table and rewritten back to the correct internal IP:port.

Q: How exactly does traceroute discover each hop's IP address?

It sends a sequence of probe packets with increasing TTL values starting from 1. Each router along the path decrements TTL by 1; the first router that sees TTL hit 0 drops the packet and replies with an ICMP "Time Exceeded" message carrying its own source IP. So probe 1 (TTL=1) reveals hop 1's IP, probe 2 (TTL=2) reveals hop 2's IP, and so on, until a probe actually reaches the destination (which replies with ICMP Echo Reply or a port-unreachable message instead).

Q: Why can't IPv6 routers fragment packets in transit like IPv4 routers can?

It was a deliberate design decision to reduce router overhead and improve reliability — in-network fragmentation is expensive (routers must reassemble/refragment, and losing one fragment forces re-sending the whole original packet). IPv6 instead requires the sending host to perform Path MTU Discovery up front (probing the smallest MTU along the entire path using ICMPv6 "Packet Too Big" responses) and fragment at the source if truly necessary, keeping routers stateless and fast.

4. Transport Layer

TCP vs UDP Basic

AspectTCPUDP
ConnectionConnection-oriented (handshake required)Connectionless
ReliabilityReliable — ACKs, retransmission, ordering guaranteedUnreliable — no delivery/order guarantee
Speed/OverheadSlower, higher overhead (headers, handshakes, ACKs)Faster, minimal overhead (8-byte header)
Flow/Congestion controlYes (sliding window, slow start, etc.)None built in (app must handle it if needed)
Header size20-60 bytes8 bytes
OrderingGuaranteed in-order deliveryNo ordering guarantee
Use casesWeb (HTTP), email, file transfer, anything needing correctnessDNS, video/audio streaming, VoIP, gaming, DHCP — latency-sensitive, loss-tolerant

When to use which: use TCP whenever correctness and completeness matter more than raw speed (a corrupted API response is worse than a slightly slower one). Use UDP when low latency matters more than perfect delivery, or when the application implements its own lightweight reliability (e.g. QUIC, real-time video where a dropped frame is preferable to a stalled stream).

TCP 3-Way Handshake Intermediate

TCP establishes a connection using three messages, synchronizing initial sequence numbers (ISNs) in both directions:

  1. SYN — client picks a random initial sequence number x and sends a segment with the SYN flag set, seq = x. "I want to talk, my starting sequence number is x."
  2. SYN-ACK — server picks its own random ISN y, replies with SYN+ACK flags set, seq = y, ack = x+1. "I acknowledge your x, here's my starting sequence number y."
  3. ACK — client replies with ACK flag set, seq = x+1, ack = y+1. "I acknowledge your y." The connection is now ESTABLISHED on both sides.

Random ISNs (not starting at 0) protect against old duplicate segments from a previous connection being mistaken for part of a new one, and make TCP sequence prediction attacks harder.

TCP Connection Termination Intermediate

TCP is full-duplex, so each direction is closed independently with its own FIN/ACK — this is why it's a 4-way handshake (sometimes collapsed to 3 messages if FIN and ACK are piggybacked):

  1. Client sends FIN (no more data from me).
  2. Server replies ACK (acknowledging the FIN).
  3. Server, once it's also done sending, sends its own FIN.
  4. Client replies ACK, then enters TIME_WAIT.

TIME_WAIT is held by whichever side sent the final ACK (usually the active closer) for typically 2×MSL (Maximum Segment Lifetime, ~30-120s depending on OS) before fully freeing the socket. Two reasons it exists: (1) ensure the final ACK is not lost — if the peer never got it and retransmits its FIN, this side can still respond; (2) let any old, delayed duplicate segments from this connection die out in the network so they can't be misinterpreted by a brand-new connection reusing the same 4-tuple (src IP/port, dst IP/port).

TCP Flow Control Intermediate

TCP uses a sliding window for flow control: the receiver advertises a receive window (rwnd) in every ACK, telling the sender exactly how many more bytes it can accept right now based on free space in its receive buffer. The sender must never have more than rwnd bytes of unacknowledged data in flight. As the receiving application reads data out of the buffer, free space grows and the receiver advertises a larger window in subsequent ACKs, "sliding" the window forward. If the receive buffer fills completely, the receiver advertises rwnd = 0 ("zero window"), pausing the sender until buffer space frees up (the sender periodically probes with a small "window probe" segment to know when to resume).

TCP Reliability Intermediate

TCP guarantees reliable, in-order, byte-stream delivery using several mechanisms working together:

  • Sequence numbers — every byte of data has an implicit sequence number, letting the receiver reorder out-of-order segments and detect gaps/duplicates.
  • Acknowledgments (ACKs) — cumulative ACKs tell the sender "I've received everything up to byte N contiguously." Modern TCP also supports SACK (Selective Acknowledgment) to report non-contiguous received ranges, avoiding unnecessary retransmission of already-received data.
  • Retransmission — triggered either by a timeout (retransmission timer expires with no ACK) or by fast retransmit (receiving 3 duplicate ACKs signals a likely single lost segment, so the sender retransmits immediately without waiting for the timer).
  • RTT estimation — TCP continuously measures round-trip time samples and computes a smoothed RTT (SRTT) and RTT variance using exponential weighted moving averages (Jacobson's algorithm), then sets the retransmission timeout (RTO) as roughly SRTT + 4×RTTVAR — long enough to avoid spurious retransmits, short enough to recover quickly from real loss.

Sockets API Basics Basic

The Berkeley sockets API is the conceptual foundation almost every language's networking library sits on top of. Server side and client side use different call sequences:

CallSidePurpose
socket()BothCreate an endpoint — returns a file descriptor representing the socket.
bind()ServerAttach the socket to a specific local IP address + port.
listen()ServerMark the socket as passive, ready to accept incoming connections, with a backlog queue size.
accept()ServerBlock until a client connects; returns a new socket dedicated to that one client (the listening socket stays free to accept more).
connect()ClientInitiate the TCP handshake to a server's IP + port.
send()/recv()BothTransfer data over the established connection.
close()BothTerminate the connection and release the file descriptor.
💡 Interview Tip

A classic trick question: "why does a busy server not run out of ports?" Answer: the listening socket stays bound to one port (e.g. 443) forever; every accepted connection gets its own separate socket identified by the full 4-tuple (client IP, client port, server IP, server port) — the server-side port doesn't change per client, only the combination of all four values needs to be unique.

Q: Why does TCP use random initial sequence numbers instead of starting at 0?

Two reasons: security (predictable sequence numbers make TCP session hijacking / spoofing attacks much easier — an attacker could guess valid sequence numbers and inject data) and correctness (if a new connection reuses the same IP/port pair as a recently closed one, a random ISN prevents old, delayed segments from the previous connection from being misinterpreted as valid data in the new one).

Q: What is the difference between a socket's "listening" state and "established" state?

A listening socket is passive — created by bind()+listen(), it never carries actual application data, it just accepts incoming connection requests and hands each one off to a brand-new socket in ESTABLISHED state (created by accept()). All actual data transfer happens over these per-connection ESTABLISHED sockets, while the original listening socket keeps accepting new clients indefinitely.

Q: Why does TCP need both a timeout-based retransmission and a fast-retransmit mechanism?

Timeout-based retransmission is the fallback that always eventually works, but the RTO is deliberately conservative (based on smoothed RTT + variance) so it can take a while to fire, wasting bandwidth on an idle connection. Fast retransmit is an optimization: three duplicate ACKs strongly imply exactly one segment was lost (the receiver keeps ACKing the last good byte as later, out-of-order segments arrive), so the sender can retransmit immediately without waiting out the full timeout — much better performance under isolated packet loss.

Q: What's the difference between TCP flow control and TCP reliability mechanisms?

Flow control (the receive window, rwnd) is about pacing — preventing the sender from sending faster than the receiver's application can consume data, protecting the receiver's buffer. Reliability (sequence numbers, ACKs, retransmission, RTT-based timeouts) is about correctness — ensuring every byte sent eventually arrives, exactly once, in the right order, even across an unreliable network that can drop, duplicate, or reorder packets. They're independent mechanisms working together.

Q: If UDP has no flow/congestion control, why is it still used for things like video streaming?

Because for real-time media, a late packet is often worse than a lost one — TCP's retransmission and in-order delivery guarantees would stall playback waiting for a resend of an old frame nobody needs anymore. UDP lets the application (or a protocol layered on it, like RTP or QUIC) decide its own tradeoffs: drop late data, use forward error correction, or implement lightweight custom congestion control tuned for latency rather than throughput.

5. Congestion Control

Slow Start Advanced

At the start of a connection (or after a timeout), TCP doesn't know the network's capacity, so it probes cautiously but grows fast: the congestion window (cwnd) starts small (historically 1 segment, commonly 10 segments with modern initial-window RFCs) and doubles every RTT — because every received ACK increases cwnd by 1 segment, and a full window's worth of ACKs arrives per RTT, this produces exponential growth. Slow start continues until cwnd reaches a threshold called ssthresh, or a loss is detected.

Congestion Avoidance Advanced

Once cwnd passes ssthresh, TCP switches from exponential to linear growth — roughly +1 segment per RTT (Additive Increase) — probing for more bandwidth gently instead of aggressively. If a loss is detected via timeout, ssthresh is reset to half the current cwnd and cwnd drops back to the slow-start floor, restarting exponential growth (this combination is called AIMD — Additive Increase, Multiplicative Decrease). AIMD's multiplicative decrease is what makes multiple competing TCP flows converge fairly to share bandwidth.

Fast Retransmit & Fast Recovery Advanced

Fast retransmit: upon 3 duplicate ACKs (implying an isolated lost segment rather than full network collapse), the sender retransmits the missing segment immediately instead of waiting for the RTO timer. Fast recovery then avoids dropping all the way back to slow start: ssthresh is halved, cwnd is set to ssthresh (+3, per duplicate ACKs already received), and TCP goes straight into congestion avoidance's linear growth — a much gentler response than a full timeout-triggered reset, because duplicate ACKs prove packets are still getting through (the network isn't fully congested, just one segment was lost).

💡 Interview Tip

Picture the classic "TCP sawtooth" graph: cwnd rises fast (slow start, exponential curve) then rises slowly (congestion avoidance, roughly straight line), until a loss event chops it down by half — repeating over and over, producing a jagged sawtooth shape over time. This shape is the visual signature interviewers expect you to describe from memory.

cwnd vs rwnd Advanced

The sender's actual usable window is always min(cwnd, rwnd): rwnd (receive window) is what the receiver advertises based on its own buffer space — a flow-control limit protecting the receiver. cwnd (congestion window) is a value the sender maintains internally based on inferred network conditions — a congestion-control limit protecting the network. Even if a receiver has a huge buffer (large rwnd), a congested network path will still throttle the sender via a small cwnd, and vice versa.

Q: Why does TCP start with a small congestion window instead of sending at full speed immediately?

Because the sender has no idea what the available bandwidth or existing congestion on the path is. Blasting at full speed immediately could overwhelm a bottleneck router's queue, causing a burst of drops. Starting small and growing exponentially (doubling per RTT) lets TCP quickly ramp up to a reasonable rate while still probing conservatively enough to back off before doing serious damage.

Q: Why does congestion avoidance grow linearly instead of exponentially like slow start?

Once cwnd is near ssthresh (an estimate of the path's capacity based on the last loss event), continuing exponential growth risks quickly overshooting capacity and causing another loss burst. Linear growth (+1 segment per RTT) is a much gentler probe — it finds a little extra available bandwidth if it exists, without aggressively flooding the path.

Q: Why does a timeout cause a full reset to slow start, but 3 duplicate ACKs only trigger fast recovery (a milder response)?

A timeout with zero ACKs arriving suggests the network path may be severely congested or broken — no information is getting through at all, so it's safest to back off aggressively and re-probe from scratch. Duplicate ACKs, by contrast, are proof that packets ARE still arriving at the receiver (just one segment out of many was lost) — so the network clearly still has some capacity, and TCP can recover more gently by halving cwnd rather than collapsing it entirely.

Q: What actually limits TCP throughput — cwnd, rwnd, or something else?

The sender's effective window is min(cwnd, rwnd), so whichever is smaller becomes the bottleneck. In practice, on modern high-bandwidth networks with adequately-sized receive buffers, cwnd (network congestion) is usually the binding constraint; but on connections with very large bandwidth-delay product and small receive buffers (e.g. default OS socket buffer sizes on a high-latency link), rwnd can become the limiter — this is why TCP window scaling exists, to allow rwnd values larger than the original 16-bit field permitted.

6. Application Layer

HTTP Basics Basic

HTTP (HyperText Transfer Protocol) is a stateless, text-based (in HTTP/1.1) request-response protocol running over TCP (or over QUIC/UDP in HTTP/3).

MethodPurposeIdempotent?Has body?
GETRetrieve a resourceYesNo (conventionally)
POSTCreate a resource / submit dataNoYes
PUTReplace a resource entirelyYesYes
PATCHPartially update a resourceNo (usually)Yes
DELETERemove a resourceYesNo (conventionally)
HEADLike GET but headers only, no bodyYesNo
OPTIONSDiscover allowed methods/CORS preflightYesNo

Statelessness: each HTTP request is independent — the server retains no memory of previous requests by default. Cookies (set via the Set-Cookie response header, sent back via the Cookie request header) are the classic mechanism to layer state (sessions, auth tokens) on top of a stateless protocol. Common headers: Content-Type, Content-Length, Authorization, Cache-Control, ETag, User-Agent, Host.

HTTP Status Codes Basic

RangeCategoryCommon examples
1xxInformational100 Continue, 101 Switching Protocols
2xxSuccess200 OK, 201 Created, 204 No Content
3xxRedirection301 Moved Permanently, 302 Found, 304 Not Modified
4xxClient Error400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 429 Too Many Requests
5xxServer Error500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout

HTTPS & TLS Intermediate

HTTPS is simply HTTP layered on top of TLS (Transport Layer Security), instead of raw TCP. TLS provides three guarantees: confidentiality (encryption — nobody in between can read the data), integrity (tampering is detectable via MACs), and authentication (the server proves its identity via a certificate signed by a trusted Certificate Authority, and optionally the client too). The TLS handshake (see the Security section below) negotiates a shared symmetric session key using asymmetric cryptography, then all subsequent HTTP traffic is encrypted with that fast symmetric key.

HTTP/1.1 vs HTTP/2 vs HTTP/3 Advanced

AspectHTTP/1.1HTTP/2HTTP/3
TransportTCPTCPQUIC (over UDP)
FormatPlain textBinary framingBinary framing (over QUIC)
MultiplexingNo — one request per connection at a time (mitigated with multiple parallel TCP connections)Yes — many streams share one TCP connectionYes — many streams share one QUIC connection
Head-of-line blockingSevere (per connection)Fixed at HTTP layer, but still present at TCP layer (one lost packet blocks all streams)Eliminated — QUIC streams are independent even at the transport layer
Header compressionNoneHPACKQPACK
Server PushNoYes (mostly deprecated/unused in practice now)Yes (same caveats)
Connection setupTCP handshake + TLS handshake (separate round trips)TCP handshake + TLS handshakeCombined transport+TLS handshake — often 1-RTT or 0-RTT on reconnect

HTTP/2's biggest win over HTTP/1.1 is multiplexing: instead of needing one request in flight per TCP connection (forcing browsers to open 6+ parallel connections per domain to hide latency), many logical streams share a single connection concurrently, interleaved as binary frames. HPACK compresses HTTP headers (which repeat heavily across requests — cookies, user-agent, etc.) using a shared, indexed table between client and server. HTTP/3 replaces TCP with QUIC (built on UDP) specifically to solve the transport-level head-of-line blocking that still plagues HTTP/2 — see the Advanced section for the full explanation.

DNS Resolution Process Intermediate

Resolving www.example.com to an IP address walks through a caching hierarchy before hitting the authoritative source:

  1. Browser cache — checks if it already resolved this hostname recently.
  2. OS cache — checks the operating system's stub resolver cache.
  3. Recursive resolver (typically your ISP's or a public one like 8.8.8.8/1.1.1.1) — if not cached, it takes over the full lookup on the client's behalf.
  4. Root DNS server — the resolver asks a root server, which doesn't know the answer but points it to the correct TLD (Top-Level Domain) server for .com.
  5. TLD server — points the resolver to the authoritative name server for example.com.
  6. Authoritative name server — holds the actual DNS records for example.com and returns the A/AAAA record for www.example.com.
  7. The resolver caches the result (per the record's TTL) and returns it to the client, which also caches it locally.

DNS Record Types Intermediate

RecordPurpose
AMaps a hostname to an IPv4 address
AAAAMaps a hostname to an IPv6 address
CNAMEAlias — maps a hostname to another hostname (which is then resolved further)
MXMail exchange — specifies the mail server(s) responsible for a domain, with priority
TXTArbitrary text — commonly used for domain verification, SPF/DKIM/DMARC email authentication
NSDelegates a domain (or subdomain) to a specific authoritative name server
PTRReverse lookup — maps an IP address back to a hostname
SOAStart of Authority — administrative info about the zone (primary server, refresh timers, etc.)

FTP & SMTP Basic

FTP (File Transfer Protocol) uses two separate TCP connections: a control connection (port 21) that stays open for commands (LOGIN, LIST, RETR, STOR), and a data connection (port 20 in active mode, or a negotiated port in passive mode) opened separately for the actual file transfer. Passive mode is more firewall/NAT-friendly since the client initiates both connections.

SMTP (Simple Mail Transfer Protocol), port 25 (or 587 for authenticated submission), is used to send mail from a client to a server or between mail servers — it's push-only. Retrieving mail from a mailbox uses separate protocols: POP3 (downloads and typically deletes from server) or IMAP (keeps mail synced on the server, supports multiple devices/folders).

WebSockets vs HTTP Polling Intermediate

  • Short polling — client repeatedly sends HTTP requests at a fixed interval asking "anything new?" Simple but wasteful (many empty responses) and adds latency up to the polling interval.
  • Long polling — client sends a request; the server holds it open without responding until new data is actually available (or a timeout), then the client immediately re-issues another request. Reduces wasted requests versus short polling but still has per-request HTTP overhead.
  • WebSockets — starts as an HTTP request that upgrades the connection (via the Upgrade: websocket header and a 101 Switching Protocols response) to a persistent, full-duplex TCP connection. After the upgrade, both client and server can push messages to each other at any time with minimal framing overhead — ideal for chat apps, live dashboards, multiplayer games, and anything needing true real-time bidirectional communication.
⚠️ Warning

WebSockets bypass the normal request/response HTTP semantics after the upgrade — meaning caches, standard HTTP auth flows, and typical middleware may not behave as expected. Load balancers must be configured to keep the same server for the life of a WebSocket connection (sticky sessions) since it's stateful and long-lived.

Q: Is HTTP really stateless — then how does login/session persistence work?

HTTP itself has no built-in concept of "this request belongs to the same user as that earlier request" — every request is handled independently by the server. State is layered on top: the server issues a session identifier (often via a Set-Cookie header), the browser automatically re-sends that cookie on every subsequent request to the same domain, and the server looks up server-side session data (or validates a self-contained token like a JWT) keyed by that identifier to reconstruct "who is this."

Q: What's the practical difference between a 401 and a 403 status code?

401 Unauthorized actually means "unauthenticated" — the request lacks valid credentials, and the client should authenticate (e.g. log in) and retry. 403 Forbidden means the client IS authenticated (the server knows who you are) but you don't have permission to access this resource, and re-authenticating won't help — a different set of credentials/permissions would be needed.

Q: Why does DNS use caching so heavily, and what does TTL control?

Without caching, every single web request would require walking the full root→TLD→authoritative chain, adding significant latency and load on root/TLD servers for the entire internet. TTL (Time To Live), set by the domain owner on each DNS record, tells every resolver/cache along the chain how many seconds it may reuse that answer before re-querying — a tradeoff between freshness (short TTL, more accurate but more lookups) and efficiency (long TTL, faster but stale data lingers longer after a change).

Q: Why is HTTP/2 multiplexing not a complete fix for head-of-line blocking?

HTTP/2 solves head-of-line blocking at the application/framing layer — multiple logical streams no longer need separate TCP connections. But all those streams still ride on a single underlying TCP connection, and TCP itself guarantees strictly in-order byte delivery. If a single TCP segment is lost, TCP must hold and buffer ALL the following bytes (across every HTTP/2 stream in that segment's path) until the lost segment is retransmitted and received — so one lost packet can stall every stream, even ones logically unrelated to the dropped data. This is exactly the problem HTTP/3's QUIC was built to fix (see the Advanced section).

Q: When would you choose long polling over WebSockets, or vice versa?

WebSockets are the better fit for frequent, low-latency, bidirectional traffic (chat, live collaboration, gaming) since the connection stays open and both sides can push at any moment with minimal overhead. Long polling can be preferable for infrequent updates, simpler infrastructure (works over plain HTTP through any proxy/load balancer without special upgrade handling), or environments where maintaining many persistent open connections is operationally harder to scale than handling bursty short-lived requests.

7. Network Security

Symmetric vs Asymmetric Encryption Intermediate

Symmetric encryption uses the same key to encrypt and decrypt. Fast and efficient for bulk data, but both parties must somehow share the secret key beforehand without anyone else obtaining it — the "key distribution problem". Example: AES — two people who both know a shared password-derived key can encrypt/decrypt messages between them using AES.

Asymmetric encryption (public-key cryptography) uses a mathematically linked key pair: a public key (shared freely) and a private key (kept secret). Data encrypted with the public key can only be decrypted with the corresponding private key, and vice versa (used for signing). Solves the key distribution problem but is computationally much slower. Example: RSA — a website publishes its public key; anyone can encrypt a message with it, but only the website's private key can decrypt it.

In practice, real systems (like TLS) use a hybrid approach: asymmetric crypto to securely exchange a random symmetric session key, then fast symmetric crypto for the actual bulk data — getting the security benefits of asymmetric key exchange with the speed of symmetric encryption.

TLS/SSL Handshake Advanced

(Simplified TLS 1.2-style flow — TLS 1.3 streamlines this to fewer round trips, but the concepts are the same):

  1. Client Hello — client sends supported TLS versions, cipher suites, and a random value.
  2. Server Hello — server picks a TLS version and cipher suite, sends its own random value, and sends its digital certificate (containing its public key, signed by a CA).
  3. Certificate verification — client verifies the certificate's chain of trust up to a CA it trusts (see PKI below), and checks the domain name matches.
  4. Key exchange — client (and, in modern Diffie-Hellman-based suites, both sides) generates key material to derive a shared symmetric session key, encrypting it with the server's public key (or using ephemeral Diffie-Hellman for forward secrecy).
  5. Finished messages — both sides send a final handshake message encrypted with the newly derived session key, confirming the handshake succeeded and integrity wasn't tampered with.
  6. All further communication is encrypted using the fast symmetric session key.

Digital Certificates & PKI Advanced

PKI (Public Key Infrastructure) is the system of trust that makes TLS certificates meaningful. A Certificate Authority (CA) is a trusted third party that verifies a domain/organization's identity and then issues a digital certificate — essentially a document binding a public key to an identity, digitally signed by the CA's own private key. Browsers and OSes ship with a built-in list of trusted root CAs. When your browser receives a server's certificate, it verifies the CA's signature using the CA's public key, potentially walking up a chain of trust (root CA → intermediate CA → leaf/server certificate) until it reaches a root it inherently trusts. If any link in this chain is broken, expired, revoked, or doesn't match the domain, the browser shows a certificate warning.

Firewalls Intermediate

  • Packet-filtering firewall — inspects each packet in isolation against static rules (source/dest IP, port, protocol). Fast but has no concept of connection state or context.
  • Stateful firewall — tracks the state of active connections (e.g. "this is part of an already-established TCP session"), so it can make smarter decisions like automatically allowing return traffic for outbound connections without an explicit rule for every possible reply.
  • Application-layer firewall (proxy/WAF) — inspects actual application data (Layer 7), understanding protocols like HTTP well enough to block SQL injection patterns, malformed requests, or specific URLs/content — much smarter but slower and more resource intensive than lower-layer filtering.

VPNs Intermediate

A VPN (Virtual Private Network) creates an encrypted tunnel between a client and a VPN server, so all traffic between them is unreadable to anyone observing the underlying network (e.g. a coffee shop Wi-Fi). It also makes the client appear to originate from the VPN server's IP/location. Conceptually, the client's packets are encapsulated inside another encrypted packet addressed to the VPN server, which decrypts and forwards them to their real destination — commonly implemented via protocols like IPsec, OpenVPN, or WireGuard.

Common Attacks Intermediate

  • MITM (Man-in-the-Middle) — an attacker secretly intercepts (and potentially alters) communication between two parties who believe they're talking directly to each other, e.g. by ARP-spoofing on a LAN or via a rogue Wi-Fi access point. TLS with proper certificate validation is the main defense — an attacker without the real server's private key can't forge a valid certificate for the domain.
  • DNS spoofing (cache poisoning) — an attacker tricks a DNS resolver into caching a fraudulent IP address for a domain, redirecting victims to a malicious server even though they typed the correct URL. Defended against with DNSSEC (cryptographically signed DNS responses).
  • SYN flood — a denial-of-service attack that sends a flood of SYN packets (often with spoofed source IPs) without ever completing the handshake with the final ACK, exhausting the server's backlog queue of half-open connections so legitimate clients can't connect. Mitigated with SYN cookies (server encodes connection state into the SYN-ACK's sequence number instead of storing it, so no server-side state is allocated until the final ACK proves the client is real).
💡 Interview Tip

If asked "how does HTTPS actually prevent a MITM attack" don't just say "encryption" — the real answer is certificate validation. Encryption alone doesn't stop an attacker from establishing their own encrypted connection with you while pretending to be the server; what stops them is that they cannot produce a valid certificate for the real domain signed by a CA your browser trusts.

Q: Why does TLS use asymmetric encryption only for the handshake and not for the whole session?

Asymmetric algorithms (RSA, Diffie-Hellman) are orders of magnitude slower than symmetric ones (AES) because they rely on expensive modular exponentiation over large numbers rather than simple bit operations. Using asymmetric crypto only briefly, to securely establish a shared symmetric session key, gets the best of both: the key-distribution security of asymmetric crypto and the raw speed of symmetric crypto for the actual bulk data transfer.

Q: What does it mean for a certificate to be "trusted", and what stops anyone from just making their own?

Anyone technically can generate a "self-signed" certificate for any domain, but it won't be trusted because browsers only trust certificates whose signature chain leads back to one of a small set of root CAs pre-installed in the OS/browser's trust store. CAs are audited and only issue certificates after verifying the requester actually controls the domain (or, for higher assurance certs, the organization's legal identity) — so a certificate being "trusted" really means "a party the browser vendor has vetted vouches that this public key belongs to this domain."

Q: How do SYN cookies defend against a SYN flood without breaking the handshake?

Normally a server allocates a queue entry for every SYN it receives, waiting for the final ACK — an attacker can exhaust this queue with fake SYNs that never complete. With SYN cookies, the server doesn't store any per-connection state after the SYN; instead it encodes the necessary state (using a cryptographic hash of the connection's IP/port/timestamp plus a secret) directly into the sequence number it sends back in the SYN-ACK. If a real client responds with the correct final ACK (proving it's a real client with a real network round trip, not a spoofed IP), the server can reconstruct the connection state from the ACK's number alone — no queue was ever needed for fake connections.

Q: What's the difference between a stateful firewall and a Web Application Firewall (WAF)?

A stateful firewall operates at the transport layer, tracking connection state (SYN sent, established, etc.) to make allow/deny decisions based on IPs, ports, and connection context — it has no idea what's actually inside an HTTP request. A WAF operates at the application layer, actually parsing HTTP requests/responses to detect and block attack patterns like SQL injection, XSS payloads, or malformed requests targeting a specific web application — much more specialized and aware of application semantics, but also more resource intensive and specific to the protocol it understands.

Q: Why is DNS particularly vulnerable to spoofing compared to, say, TCP?

Classic DNS (without DNSSEC) has no cryptographic authentication of responses — a resolver mostly just trusts that a UDP response claiming to answer its query, with a matching transaction ID and source port, is legitimate. Since UDP is connectionless and the transaction ID space is relatively small, an attacker who can guess or brute-force the right transaction ID/port combination (or is positioned to intercept traffic) can inject a forged response before the real authoritative server's answer arrives, poisoning the resolver's cache for all its future users until the TTL expires.

8. Advanced & Rare Topics

These go beyond typical interview depth — the kind of questions that separate candidates who've memorized definitions from those who've actually reasoned about how production networks behave under load.

Reno vs CUBIC vs BBR Advanced

TCP Reno — the classic AIMD algorithm described earlier (slow start, congestion avoidance, fast retransmit/recovery). Its linear +1-per-RTT growth in congestion avoidance was designed for the modest bandwidth-delay products of the 1990s; on today's high-bandwidth, high-latency links it takes far too long to ramp cwnd back up after any loss, badly underutilizing available bandwidth.

TCP CUBIC (the Linux default for years) — grows cwnd as a cubic function of time since the last loss event, rather than linearly per-RTT. This makes growth independent of RTT (fairer to flows with different round-trip times, unlike Reno where short-RTT flows grow faster and dominate bandwidth) and lets cwnd approach the previous pre-loss value quickly, then probe more aggressively for new capacity beyond it, plateauing near the last known "safe" point before pushing further.

BBR (Bottleneck Bandwidth and RTT), developed at Google — a fundamentally different model. Instead of treating packet loss as the primary congestion signal (which conflates real congestion with the small, harmless losses common on modern lossy links like Wi-Fi), BBR actively models the network path by estimating two things: the bottleneck link's actual available bandwidth and the minimum RTT (an estimate of the path with an empty queue). It paces sending to match the estimated bandwidth directly rather than reactively backing off after loss, aiming to keep queues nearly empty (avoiding "bufferbloat") while still fully utilizing the bottleneck link. This makes BBR notably more effective on lossy or highly variable-latency links than loss-based algorithms.

Head-of-Line Blocking — HTTP/2 vs HTTP/3 Advanced

Head-of-line (HOL) blocking is when one stalled/delayed unit of data blocks unrelated data behind it in the same queue/channel from being processed, even though that other data is ready.

HTTP/1.1 suffers HOL blocking at the request level — only one request can be outstanding per connection (without pipelining, which is barely used), so a slow response blocks everything queued behind it on that connection.

HTTP/2 fixes that specific problem via multiplexing — many logical streams share one TCP connection concurrently. But it introduces (or rather, fails to remove) HOL blocking at the transport layer: because TCP guarantees strictly in-order byte delivery for the whole connection, a single lost TCP segment forces the OS/TCP stack to withhold ALL bytes after it — even bytes belonging to completely unrelated HTTP/2 streams that arrived fine — until the lost segment is retransmitted and delivered. One dropped packet stalls everything.

HTTP/3 / QUIC solves this by moving stream multiplexing into the transport protocol itself, built on UDP rather than TCP. QUIC maintains independent per-stream sequencing and loss recovery: if a packet carrying data for stream A is lost, only stream A's delivery is delayed — streams B, C, D etc. carried in other packets continue to be delivered and processed immediately, with no transport-level blocking between unrelated streams. This is the core reason HTTP/3 exists: it isn't really about "HTTP over UDP" being inherently better, it's that TCP's single ordered byte-stream abstraction is fundamentally incompatible with independent multiplexed streams, and QUIC reimplements TCP-like reliability (ACKs, retransmission, congestion control) per-stream instead of per-connection.

Load Balancing Algorithms Advanced

  • Round robin — requests are distributed to backend servers in fixed rotating order. Simple, but ignores actual server load or request cost — a slow server gets the same share as a fast one.
  • Least connections — routes each new request to whichever backend currently has the fewest active connections, adapting to real-time load imbalance better than blind rotation.
  • Consistent hashing — maps both servers and request keys onto a conceptual hash ring; each request is routed to the nearest server clockwise on the ring from its own hash position. The key property: when a server is added or removed, only the keys that mapped near that server need to be remapped — everything else stays put. This matters enormously for caching layers (e.g. a distributed cache/CDN) because naive hashing (like hash(key) % N) would remap almost every key when N (the number of servers) changes, causing a massive cache-miss stampede; consistent hashing minimizes redistribution to roughly 1/N of keys, keeping cache hit rates stable during scaling events.

CDN Mechanics Advanced

A CDN (Content Delivery Network) caches content at many geographically distributed edge servers (Points of Presence) close to end users, so requests don't have to travel all the way to a distant origin server. Two mechanisms are central to how this works:

  • Edge caching — the first request for a resource at a given edge location is a cache miss (fetched from origin and stored), and subsequent requests from nearby users are served directly from that edge cache, dramatically cutting latency and origin load. Cache freshness is controlled via HTTP caching headers (Cache-Control, ETag, TTLs).
  • Anycast routing — the same IP address is announced via BGP from many different physical locations around the world simultaneously. Normal internet routing (which inherently picks the "shortest"/cheapest AS-path) naturally directs each user's traffic to whichever announcing location is topologically closest, with zero client-side logic required — the network itself does the geographic load distribution.

NAT Traversal for P2P: STUN/TURN/ICE Advanced

Two peers both sitting behind NAT routers can't simply connect to each other directly — neither has a publicly routable address the other can dial. A family of protocols solves this:

  • STUN (Session Traversal Utilities for NAT) — a peer asks a public STUN server "what's my public IP and port, as you see them?" This reveals the peer's own external NAT mapping, which it can then share with the other peer (via a signaling channel) to attempt a direct connection — works when the NAT type allows predictable/consistent port mapping.
  • TURN (Traversal Using Relays around NAT) — the fallback when direct connection isn't possible (e.g. symmetric NATs on both sides). A TURN server relays all traffic between the two peers, sacrificing the efficiency of a direct P2P path for guaranteed connectivity — every packet now makes an extra hop through the relay.
  • ICE (Interactive Connectivity Establishment) — the overall framework (used by WebRTC, for example) that gathers all possible connection candidates — local addresses, STUN-discovered public mappings, and TURN relay addresses — from both peers, then systematically tries them in priority order (direct P2P first, since it's fastest and cheapest) until one actually works, falling back to TURN relay only as a last resort.

BGP Path Selection Attributes Advanced

Unlike interior routing protocols that simply pick the numerically shortest/cheapest path, BGP selects routes based on a sequence of policy attributes evaluated in order until a tiebreaker resolves the choice — reflecting that the internet is a web of independent businesses with commercial/political routing preferences, not just a pure shortest-path problem:

  1. Highest Local Preference — an operator's own configured preference for outbound traffic (e.g. prefer a cheaper transit provider).
  2. Shortest AS-Path — fewer autonomous systems to cross (BGP's rough analog of "hop count", though it's really about administrative boundaries, not physical distance).
  3. Lowest origin type, then lowest MED (Multi-Exit Discriminator) — a hint from a neighboring AS about which of several entry points it prefers you use.
  4. eBGP over iBGP preference, then lowest IGP metric to the next hop, and finally the lowest router ID as a last-resort tiebreaker.

The key interview insight: BGP is fundamentally a policy routing protocol, not a shortest-path protocol — the "best" route is whatever the local network operator's configured business preferences say it is, which is why the internet's actual traffic paths often don't correspond to the geographically or technically shortest route.

TIME_WAIT and Port Exhaustion Advanced

As covered in the Transport Layer section, TIME_WAIT is held by the side that sends the final ACK of connection termination, for roughly 2×MSL, specifically to (a) absorb a lost final ACK by being able to respond to a retransmitted FIN, and (b) let any straggling duplicate segments from the old connection expire in the network before a new connection could reuse the exact same 4-tuple and misinterpret them.

The exact purpose matters for a subtle but real production problem: a socket in TIME_WAIT still holds its local port reserved and can't be reused for a new outbound connection to the same remote IP:port for the duration of the wait. Under very high connection churn — e.g. a server or proxy opening and closing huge numbers of short-lived outbound TCP connections to the same destination (a common pattern for load testing tools, or a backend repeatedly calling the same downstream service without connection pooling) — the pool of available ephemeral local ports (typically ~28,000-64,000 depending on OS config) can be exhausted faster than TIME_WAIT sockets expire, causing new connect() calls to fail outright ("port exhaustion" / EADDRNOTAVAIL).

Common mitigations: connection pooling/reuse (avoid opening a fresh connection per request at all), the SO_REUSEADDR/SO_REUSEPORT socket options (let a new socket bind to a port still lingering in TIME_WAIT under safe conditions), widening the ephemeral port range, and reducing the TIME_WAIT duration via OS tuning (with care, since it weakens the original safety guarantees).

⚠️ Common Pitfall

A very common interview trap: "doesn't TIME_WAIT mean the server can only handle one connection every 2×MSL?" No — TIME_WAIT is per 4-tuple (local IP:port, remote IP:port), not global to the port. A server listening on port 443 can have thousands of simultaneous ESTABLISHED and TIME_WAIT connections at once, because each client has a different IP:port combination. Port exhaustion is primarily a concern for the side making many outbound connections to the same fixed destination, not for a typical server accepting varied inbound clients.

Q: Why does BBR outperform loss-based algorithms like CUBIC on lossy links (e.g. Wi-Fi or cellular)?

Loss-based algorithms like CUBIC and Reno treat any packet loss as a congestion signal and back off cwnd — but on wireless links, a meaningful fraction of loss is just random transmission error, completely unrelated to actual queue congestion. This causes loss-based algorithms to needlessly throttle throughput on links that could actually handle more traffic. BBR instead directly measures bottleneck bandwidth and minimum RTT to build its own model of the path's true capacity, so it doesn't conflate random bit errors with real congestion, and can sustain much higher throughput on imperfect links.

Q: Concretely, how does QUIC avoid the transport-level head-of-line blocking that HTTP/2 over TCP suffers from?

TCP multiplexes all HTTP/2 streams into one single ordered byte stream at the transport layer, so the OS kernel can't hand any data to the application until every earlier byte (regardless of which logical stream it belongs to) has arrived — a single lost packet blocks all streams. QUIC instead implements reliability and ordering independently per logical stream within a single UDP-based connection: loss of a packet carrying stream A's data only stalls stream A; packets carrying stream B or C's data can still be delivered and processed by the application immediately, since QUIC doesn't force one single global byte ordering across unrelated streams.

Q: Why is consistent hashing specifically important for a distributed cache, more than for a generic load balancer?

With a generic stateless load balancer, it doesn't really matter which server handles which request — round robin or least-connections work fine since any server can serve any request equally well. But a distributed cache's whole value depends on a given key consistently landing on the same server so that repeated lookups actually hit a warm cache. With naive modulo hashing, adding or removing even one cache server remaps almost every key to a different server (since N changed), causing a massive simultaneous cache-miss storm across the whole system right when the cluster is already in a vulnerable scaling event. Consistent hashing bounds this remapping to roughly 1/N of keys, keeping the cache mostly warm through scaling changes.

Q: Why does anycast work for CDN routing without any application-level logic deciding "which edge server is closest"?

Anycast exploits how BGP already works: the same IP prefix is announced from multiple physical locations, and every router along the way independently applies its normal best-path selection (shortest AS-path, lowest cost, etc.) when forwarding traffic toward that IP. Since "shortest/cheapest path" in BGP terms usually correlates reasonably well with network proximity, a user's packets naturally get routed to whichever announcing edge location the internet's normal routing decisions consider closest — no DNS-based geolocation or client logic is required, the effect emerges purely from standard routing behavior.

Q: Why can't you just set TIME_WAIT to a very short duration to avoid port exhaustion entirely?

Because TIME_WAIT's duration (2×MSL) is specifically sized to guarantee any packet from the old connection has fully expired and left the network before a new connection could reuse the same 4-tuple — cutting it too short reintroduces the exact correctness bug it exists to prevent: a stray delayed packet from a previous connection could be misdelivered into a brand new, unrelated connection that happens to reuse the same IP/port pair, corrupting its data stream. The right fix for port exhaustion is almost always reducing unnecessary connection churn (pooling/reusing connections) rather than weakening TIME_WAIT's safety window.

9. Practical Coding

A minimal TCP client-server pair using POSIX sockets in C++. The server listens for connections and echoes back whatever the client sends; the client connects, sends a message, and prints the echoed reply. This is the canonical "hello world" of socket programming and a common whiteboard/take-home exercise.

TCP Echo Server (C++) Intermediate

C++
// server.cpp — minimal TCP echo server (POSIX sockets)
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

int main() {
    const int PORT = 8080;
    const int BACKLOG = 10;
    char buffer[1024];

    // 1. Create a TCP socket (IPv4, stream socket)
    int serverFd = socket(AF_INET, SOCK_STREAM, 0);
    if (serverFd < 0) {
        std::cerr << "socket() failed\n";
        return 1;
    }

    // Allow quick restart of the server on the same port (avoid TIME_WAIT bind errors)
    int opt = 1;
    setsockopt(serverFd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    // 2. Bind the socket to an address + port
    sockaddr_in address{};
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;   // listen on all local interfaces
    address.sin_port = htons(PORT);         // host-to-network byte order

    if (bind(serverFd, (sockaddr*)&address, sizeof(address)) < 0) {
        std::cerr << "bind() failed\n";
        return 1;
    }

    // 3. Mark the socket as passive / ready to accept connections
    if (listen(serverFd, BACKLOG) < 0) {
        std::cerr << "listen() failed\n";
        return 1;
    }

    std::cout << "Server listening on port " << PORT << "...\n";

    while (true) {
        sockaddr_in clientAddr{};
        socklen_t clientLen = sizeof(clientAddr);

        // 4. Block until a client connects; get a NEW socket for this client
        int clientFd = accept(serverFd, (sockaddr*)&clientAddr, &clientLen);
        if (clientFd < 0) {
            std::cerr << "accept() failed\n";
            continue;
        }

        std::cout << "Client connected: "
                   << inet_ntoa(clientAddr.sin_addr) << ":"
                   << ntohs(clientAddr.sin_port) << "\n";

        // 5. Read the client's message and echo it straight back
        memset(buffer, 0, sizeof(buffer));
        ssize_t bytesRead = read(clientFd, buffer, sizeof(buffer) - 1);
        if (bytesRead > 0) {
            std::cout << "Received: " << buffer << "\n";
            send(clientFd, buffer, bytesRead, 0);
        }

        // 6. Close this client's connection (listening socket stays open for more)
        close(clientFd);
    }

    close(serverFd);
    return 0;
}

// Compile:  g++ -o server server.cpp
// Run:      ./server

TCP Echo Client (C++) Intermediate

C++
// client.cpp — minimal TCP echo client (POSIX sockets)
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

int main() {
    const int PORT = 8080;
    const char* SERVER_IP = "127.0.0.1";
    char buffer[1024];

    // 1. Create a TCP socket
    int sockFd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockFd < 0) {
        std::cerr << "socket() failed\n";
        return 1;
    }

    // 2. Describe the server we want to connect to
    sockaddr_in serverAddr{};
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(PORT);

    if (inet_pton(AF_INET, SERVER_IP, &serverAddr.sin_addr) <= 0) {
        std::cerr << "Invalid server address\n";
        return 1;
    }

    // 3. Connect — performs the TCP 3-way handshake under the hood
    if (connect(sockFd, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
        std::cerr << "connect() failed\n";
        return 1;
    }

    // 4. Send a message to the server
    const char* message = "Hello from the client!";
    send(sockFd, message, strlen(message), 0);
    std::cout << "Sent: " << message << "\n";

    // 5. Read the echoed reply back
    memset(buffer, 0, sizeof(buffer));
    ssize_t bytesRead = read(sockFd, buffer, sizeof(buffer) - 1);
    if (bytesRead > 0) {
        std::cout << "Echoed back: " << buffer << "\n";
    }

    // 6. Close the connection (triggers the 4-way FIN termination)
    close(sockFd);
    return 0;
}

// Compile:  g++ -o client client.cpp
// Run:      ./client   (after starting ./server in another terminal)
💡 Interview Tip

Be ready to explain each socket call in terms of the OSI/TCP model as you write this code: socket() just allocates a file descriptor; bind() and listen() are server-only setup steps; accept() blocks until a TCP 3-way handshake completes and hands you a brand-new per-client socket; connect() on the client actively initiates that handshake. This is exactly the kind of "connect the theory to the code" question that separates candidates who've memorized TCP theory from those who understand it.

Q: In the server code, why do we call accept() in a loop instead of just once?

The listening socket (serverFd) is a long-lived, passive socket dedicated only to accepting new connections — it never carries application data itself. Each call to accept() blocks until one client completes the handshake, then returns a brand new socket (clientFd) dedicated to that specific client. Looping on accept() lets the server keep handling one client after another indefinitely (or, in a real production server, spawn a thread/process per accepted connection, or use an event loop, to handle many clients concurrently instead of serially).

Q: What does htons() do, and why is it needed here?

Different CPU architectures store multi-byte numbers in different byte orders (endianness) — but network protocols require a single agreed-upon order, "network byte order," which is big-endian. htons() ("host to network short") converts a 16-bit value (like a port number) from whatever the local machine's native byte order is into network byte order before it's placed in a packet header, so the receiving machine (which may have different native endianness) interprets the value correctly.

Q: This example is purely synchronous/blocking and handles one client at a time. How would you make it handle many clients concurrently?

Common approaches: (1) spawn a new thread or fork a new process per accepted client connection, so each runs its own blocking read/write loop independently; (2) use non-blocking sockets with an event-driven I/O multiplexing mechanism like select()/poll()/epoll() (Linux) or IOCP (Windows) to handle many sockets in a single thread, reacting only when a socket actually has data ready; (3) use a higher-level async framework/library (e.g. boost::asio, libuv) that wraps these primitives. Production servers almost always use option 2 or 3 for scalability, since one-thread-per-connection doesn't scale well past a few thousand concurrent clients.

References & Further Reading