Networking Fundamentals and Protocols Questions
The core model of how networks move data: the OSI and TCP/IP layers, the Internet Protocol suite, transport protocols (TCP versus UDP), encapsulation, and TCP behavior including congestion control. Covers the protocol foundations every networking and infrastructure discussion builds on, from link layer through transport. The conceptual bedrock beneath addressing, routing, and switching.
QUIC runs over UDP but provides multiplexed, reliable streams without the head-of-line blocking that a single TCP connection carrying multiple HTTP/2 streams suffers from. Explain, at a conceptual level, how QUIC achieves reliability and ordering per-stream without TCP's single shared sequence space, and why building this on UDP rather than extending TCP was the practical choice.
Sample Answer
Direct answer
QUIC achieves per-stream reliability and ordering without TCP's single shared sequence space by giving each logical stream its OWN independent sequence numbering and loss recovery, all multiplexed inside one UDP-based connection; a lost packet carrying data for stream A doesn't block already-arrived data for stream B from being delivered to the application, which is exactly the head-of-line blocking that a single TCP connection carrying multiple HTTP/2 streams suffers from.
Structured elaboration
Over plain TCP, HTTP/2 multiplexes many logical request/response streams onto ONE underlying TCP connection with ONE sequence-number space; TCP itself has no concept of "streams" at all, it just sees one ordered byte stream. If a single TCP segment is lost, TCP's in-order delivery guarantee means EVERY byte after that point, even bytes belonging to a completely different, otherwise-unaffected HTTP/2 stream, is held back until the lost segment is retransmitted and recovered. This is transport-level head-of-line blocking: the transport's OWN reliability guarantee (strict ordering) becomes a liability once a single connection is asked to carry multiple independent logical streams.
QUIC solves this by moving stream multiplexing INTO the transport layer itself, rather than layering it on top of a single ordered byte stream: each stream has its own sequence space and its own independent reliability/ordering guarantee, so a loss affecting one stream's data only blocks THAT stream's delivery, other streams' already-arrived data can be delivered to the application immediately, with no need to wait.
Worked example
Practically, building this on UDP (rather than trying to retrofit multi-stream awareness into TCP itself) was the pragmatic choice because TCP's behavior is deeply embedded in operating system kernels, network hardware, and middleboxes across the internet; changing TCP's fundamental semantics at that scale is far harder than building a new protocol on top of UDP, which is deliberately minimal and gives QUIC a mostly-blank slate to define its own semantics, while still being able to traverse the internet's existing UDP-forwarding infrastructure without needing every router and middlebox to understand the new protocol.
Trade-offs & pitfalls
This is a genuine, structural advantage for workloads with multiple independent logical streams sharing a connection (which is exactly the HTTP/2-and-later web-traffic pattern), but it isn't a free win for every workload: an application sending one single, purely sequential stream of data (like a straightforward large-file download) doesn't gain the same benefit from per-stream independence, since it only has one stream to begin with, the head-of-line-blocking problem QUIC solves specifically arises from MULTIPLE independent streams sharing one connection.
Implement a simple reliable stop-and-wait protocol over UDP in Python: a send_reliable(sock, dest, payload, timeout) and a matching receive_reliable(sock). Use a single-bit sequence number, ACK packets, retransmit-on-timeout, in-order delivery, and duplicate handling. Explain what your implementation demonstrates about which parts of TCP's reliability UDP does not give you for free.
Sample Answer
Direct answer
Building reliability on top of UDP means implementing, by hand, the exact machinery TCP gives you for free: a sequence number to detect duplicates, explicit acknowledgments, and a retransmission timer. A single-bit (0/1) sequence number is enough for stop-and-wait specifically, because only one message is ever in flight at a time.
Structured elaboration (approach)
send_reliable sends the payload tagged with the current sequence bit, then blocks (with a timeout) waiting for a matching ACK; on a timeout it just resends the same packet, and on receiving an ACK for the WRONG sequence number (a stale ACK from a previous round) it keeps waiting rather than treating that as success. receive_reliable accepts a packet, immediately ACKs it (even if it's a duplicate, in case its own previous ACK was lost), and only hands NEW data (matching the expected next sequence bit) up to the caller, silently absorbing duplicates.
Worked example (code)
import socket, struct
HEADER = struct.Struct("!BB") # (sequence bit, type: 0=DATA, 1=ACK)
def send_reliable(sock, dest, payload, timeout, max_retries=5, state={"seq": 0}):
seq = state["seq"]
packet = HEADER.pack(seq, 0) + payload
sock.settimeout(timeout)
for _ in range(max_retries):
sock.sendto(packet, dest)
try:
data, addr = sock.recvfrom(4096)
except socket.timeout:
continue # retransmit on timeout
if len(data) < HEADER.size:
continue
ack_seq, ack_type = HEADER.unpack(data[:HEADER.size])
if ack_type == 1 and ack_seq == seq:
state["seq"] = 1 - seq
return True
return False
def receive_reliable(sock, state={"expected_seq": 0}):
while True:
data, addr = sock.recvfrom(4096)
if len(data) < HEADER.size:
continue
seq, pkt_type = HEADER.unpack(data[:HEADER.size])
if pkt_type != 0:
continue
payload = data[HEADER.size:]
sock.sendto(HEADER.pack(seq, 1), addr) # always ACK, even duplicates
if seq == state["expected_seq"]:
state["expected_seq"] = 1 - seq
return payload, addr
# else: duplicate, already ACKed above, loop for the real next message
This was executed against a deterministic loss-simulating wrapper (a UDP socket wrapper that drops a configurable fraction of outgoing packets using a seeded random generator, so the test is reproducible) sending 4 messages at loss rates of 0%, 30%, and 60%. At every loss rate tested, all 4 messages were delivered exactly once, in the original order, confirming both the retransmit-on-timeout path and the duplicate-suppression path work correctly under real, repeated loss.
Trade-offs & pitfalls (edge cases and complexity)
Complexity: with a single sequence bit and no pipelining, stop-and-wait can send only ONE unacknowledged message at a time, so throughput is bounded by one round trip per message (a real reliability layer would need a sliding window of sequence numbers, not just one bit, to use a high-bandwidth-delay-product link efficiently, exactly the same motivation as TCP's own window). Edge cases handled: a lost DATA packet (sender times out, retransmits), a lost ACK (receiver gets a duplicate DATA packet, re-ACKs it without re-delivering the payload to the application), and a delayed ACK arriving after the sender has already given up and retransmitted (the sender must ignore an ACK for the WRONG sequence number rather than treating it as confirmation, otherwise a stale ACK could be mistaken for acknowledging the NEXT message). What this exercise demonstrates: TCP is doing exactly this kind of bookkeeping (and much more, for a full sliding window, congestion control, and out-of-order buffering) on every connection, for free.
Describe the UDP header fields (source port, destination port, length, checksum) and explain how the UDP checksum behaves differently across IPv4 and IPv6. If you suspected corrupted UDP payloads reaching an application in production, what would that suggest about where in the stack the corruption is happening?
Sample Answer
Direct answer
The UDP header is deliberately minimal, just four fields: source port, destination port, length, and checksum, and it provides no reliability, no ordering, and no flow or congestion control at all. The checksum is optional over IPv4 (it can be all-zeros to mean "not computed") but MANDATORY over IPv6, since IPv6 dropped the network-layer checksum that IPv4 had, leaving UDP's checksum as the only integrity check left covering the payload for that traffic.
Structured elaboration
- Source port (16 bits): the sending application's port, allowing a reply to be addressed back to the right process; can legitimately be zero if no reply is expected.
- Destination port (16 bits): identifies which application on the receiving host should get the datagram.
- Length (16 bits): the total length of the UDP header plus payload, in bytes, this is how a receiver knows where the datagram actually ends (UDP has no separate "end of message" marker otherwise).
- Checksum (16 bits): a checksum computed over a pseudo-header (which includes the source/destination IP addresses, borrowed conceptually from the IP layer to catch certain misdelivery errors) plus the UDP header and payload.
Over IPv4, the sender is technically permitted to skip computing the checksum entirely, since IPv4 packets already carry a header checksum which catches SOME corruption, though notably NOT payload corruption. Over IPv6, sending a UDP checksum is mandatory, precisely because IPv6 has no header checksum of its own at all, so UDP's checksum became the last line of defense for detecting corruption anywhere in the packet.
Worked example
If corrupted UDP payloads are reaching an application in production despite the checksum being enabled, that's actually a meaningful signal about WHERE the corruption is happening: a valid checksum plus corrupted payload data can only mean the corruption happened AFTER the checksum was computed and BEFORE the packet was actually transmitted onto the wire (for instance, in host memory, in a buggy driver, or in hardware), or that checksum offloading to the NIC is misconfigured or buggy (many NICs compute the checksum in hardware rather than the OS, and a broken offload implementation can silently produce or accept bad checksums). It would NOT typically indicate ordinary in-transit bit-flip corruption, since that's exactly the class of error the checksum exists to catch and reject.
Trade-offs & pitfalls
The 16-bit checksum, while better than nothing, is not cryptographically strong and won't catch every possible corruption pattern, especially certain kinds of systematic bit errors; applications with strict data-integrity requirements over UDP (like some real-time media or gaming protocols) often layer their own additional integrity or authentication checks on top rather than relying on the UDP checksum alone.
Sketch the TCP header at a high level and describe the fields most relevant to reliability and ordering: sequence number, acknowledgment number, the SYN/ACK/FIN/RST flags, window size, and the key TCP options (MSS, window scale, SACK-permitted, timestamps). If you were triaging a performance incident and could only look at a handful of these fields, which would you check first and why?
Sample Answer
Direct answer
The TCP header carries, at minimum, a sequence number and acknowledgment number (for tracking and confirming data), the SYN/ACK/FIN/RST control flags (for connection setup and teardown), a window size (for flow control), and a set of options including MSS (Maximum Segment Size), window scale, SACK-permitted (Selective Acknowledgment), and timestamps (all negotiated at the handshake). If you could only check a few during a performance incident, window size and the options negotiated at the handshake (MSS, window scale, SACK) are the highest-value first checks, since they directly bound how efficiently the connection CAN perform, before even looking at anything dynamic.
Structured elaboration
- Sequence number: identifies the position, in bytes, of this segment's data within the overall byte stream; every byte sent gets a sequence number.
- Acknowledgment number: when the ACK flag is set, indicates the NEXT byte the receiver expects, effectively confirming everything before that point has arrived.
- Flags (SYN/ACK/FIN/RST): SYN initiates a connection, ACK confirms received data (present on nearly every segment after the handshake), FIN requests a graceful close, RST aborts the connection immediately.
- Window size: the receiver's advertised available buffer space (subject to the negotiated window SCALE factor from the handshake), the mechanism behind flow control.
- Options (MSS, window scale, SACK-permitted, timestamps): negotiated ONLY in the SYN/SYN-ACK exchange and fixed for the connection's lifetime; MSS caps the largest single segment, window scale extends the effective window size beyond the raw 16-bit field, SACK-permitted enables selective (rather than only cumulative) acknowledgment, and timestamps support accurate RTT measurement and protect against stale, wrapped sequence numbers.
Worked example
Triaging a performance incident with limited time, check the negotiated OPTIONS first: if window scale never negotiated successfully (visible by comparing the SYN and SYN-ACK), the connection is capped at a 64KB window for its ENTIRE lifetime regardless of anything else, a hard, structural ceiling worth ruling out before looking at anything dynamic. Then check the CURRENT window size value on live segments (has it collapsed to something small, suggesting a flow-control-limited receiver) alongside the flags (any unexpected RSTs indicating the connection is being torn down and re-established repeatedly, itself a red flag). Sequence and acknowledgment numbers matter most for confirming specific loss/retransmission behavior (comparing them across segments), a more detailed, second-pass check once the higher-level structural questions (options, window, flags) have been ruled out.
Trade-offs & pitfalls
It's easy to over-focus on sequence and acknowledgment numbers first because they feel like "the real data" of TCP's bookkeeping, but for a FIRST-PASS performance triage, the options negotiated once at the handshake (which structurally CAP what the connection can ever achieve) and the live window size (which shows whether that cap is even being approached) are higher-leverage checks, they answer "is there a hard ceiling here" before you spend time analyzing moment-to-moment sequence-level behavior.
Explain TCP Selective Acknowledgment (SACK): how SACK blocks are represented in the TCP options, and how SACK lets a sender avoid retransmitting segments the receiver already has after a single loss event. What does a sender do differently once SACK is enabled versus a sender using only cumulative ACKs?
Sample Answer
Direct answer
Selective Acknowledgment (SACK) lets a receiver tell the sender exactly which non-contiguous blocks of data it has ALREADY received, so after a loss the sender only has to retransmit the specific missing segment(s), not everything that came after it.
Structured elaboration
Without SACK, TCP uses cumulative acknowledgment: an ACK only confirms "I have received everything up through this byte, contiguously." If segment 3 of a 10-segment flight is lost but segments 4 through 10 all arrive fine, the receiver can only ACK up through the end of segment 2, it has no way to tell the sender "I actually already have 4 through 10, I'm just missing 3." A sender using only cumulative ACKs, upon detecting the loss, may end up retransmitting segments 3 through 10 (everything the receiver hasn't cumulatively acknowledged), even though 4 through 10 were never actually lost.
With SACK enabled (negotiated via a permitted option in the handshake, then carried on subsequent ACKs), the receiver's ACK can include SACK blocks, explicit ranges of sequence numbers it holds that are NOT contiguous with the main acknowledged run, in the example above, a SACK block spanning segments 4 through 10. Now the sender knows precisely that only segment 3 needs retransmitting.
Worked example
Say a sender has segments with sequence ranges [1000-1500), [1500-2000), [2000-2500), ... up to [4500-5000), and segment [2000-2500) is lost in transit while everything else arrives. Without SACK: the receiver's ACKs stay pinned at ack=2000 (the last contiguous byte received) even as segments up through 5000 keep arriving; the sender, upon detecting the loss (via duplicate ACKs all saying ack=2000), knows only that SOMETHING after 2000 needs resending and, in older/naive implementations, could resend everything from 2000 onward. With SACK: the same duplicate ACKs at ack=2000 now also carry a SACK block like sack=2500-5000, telling the sender explicitly that only the single segment [2000-2500) is actually missing, so it retransmits exactly that one segment and nothing else.
Trade-offs & pitfalls
SACK is most valuable on connections with a large amount of data in flight (a large window relative to segment size) and where losses are isolated rather than in a solid burst, since that's exactly the scenario where "retransmit everything after the gap" wastes the most bandwidth compared to "retransmit only the gap." On a connection with a tiny window, or where an entire flight is lost at once (nothing left to selectively acknowledge), SACK provides little advantage.
Unlock Full Question Bank
Get access to all 34 Networking Fundamentals and Protocols interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.