Computer Networks Interview Questions Explained — TCP, DNS, HTTP and Beyond
Every backend engineer, DevOps engineer, and full-stack developer eventually sits across from an interviewer who asks 'What happens when you type a URL into a browser?' That question alone can make or break a senior-level interview. Networking isn't just a theoretical subject — it's the invisible infrastructure that your APIs, databases, and microservices live on. Understanding it deeply separates candidates who just write code from engineers who understand systems.
The OSI Model — Why 7 Layers Actually Matter in Practice
The OSI (Open Systems Interconnection) model is a framework that breaks network communication into 7 distinct layers. Most people memorize the names ('Please Do Not Throw Sausage Pizza Away') and stop there. That's a mistake. Understanding what each layer is responsible for helps you debug real problems.
When your HTTP request fails, is it a DNS issue (Layer 7/5), a TCP connection problem (Layer 4), or a routing issue (Layer 3)? Knowing the layers lets you mentally narrow down where the fault is, just like a doctor using anatomy to diagnose illness.
In practice, you rarely work below Layer 4 (Transport) unless you're writing embedded systems or kernel code. But you absolutely need to understand Layers 3, 4, and 7 — IP addressing, TCP/UDP, and application protocols — because they appear in every production debugging scenario, from a failing API call to a slow database connection.
Here's the critical insight: layers are about separation of concerns. Each layer only talks to the layer directly above and below it. That's why you can swap out Wi-Fi for Ethernet (Layer 1/2 change) without rewriting your HTTP code (Layer 7). The abstraction is intentional and powerful.
import socket import struct # This demo shows Layer 3 (Network) and Layer 4 (Transport) in action # We're manually building a TCP connection and sending an HTTP request # so you can SEE the layers working together def demonstrate_osi_layers(): target_host = "example.com" target_port = 80 # Layer 4 (Transport): port number identifies the service # Layer 3 (Network): DNS resolves the hostname to an IP address # This is where 'example.com' becomes '93.184.216.34' resolved_ip = socket.gethostbyname(target_host) print(f"[Layer 3 - Network] Resolved '{target_host}' -> IP: {resolved_ip}") # Layer 4 (Transport): We create a TCP socket # TCP guarantees delivery and ordering — unlike UDP which is fire-and-forget tcp_socket = socket.socket( socket.AF_INET, # Address Family: IPv4 (Layer 3) socket.SOCK_STREAM # Socket Type: TCP stream (Layer 4) ) print(f"[Layer 4 - Transport] Opening TCP connection to {resolved_ip}:{target_port}") tcp_socket.connect((resolved_ip, target_port)) print(f"[Layer 4 - Transport] TCP 3-way handshake complete (SYN -> SYN-ACK -> ACK)") # Layer 7 (Application): HTTP is the application protocol # Notice how HTTP doesn't know or care about TCP internals # That's the beauty of OSI layer separation http_request = ( f"GET / HTTP/1.1\r\n" f"Host: {target_host}\r\n" f"Connection: close\r\n" f"\r\n" ) print(f"[Layer 7 - Application] Sending HTTP GET request...") tcp_socket.sendall(http_request.encode('utf-8')) # Receive the first 512 bytes of the response raw_response = tcp_socket.recv(512) response_text = raw_response.decode('utf-8', errors='replace') # Extract just the HTTP status line (first line of the response) status_line = response_text.split('\r\n')[0] print(f"[Layer 7 - Application] HTTP Response: {status_line}") tcp_socket.close() print(f"[Layer 4 - Transport] TCP connection closed (FIN -> FIN-ACK)") demonstrate_osi_layers()
[Layer 4 - Transport] Opening TCP connection to 93.184.216.34:80
[Layer 4 - Transport] TCP 3-way handshake complete (SYN -> SYN-ACK -> ACK)
[Layer 7 - Application] Sending HTTP GET request...
[Layer 7 - Application] HTTP Response: HTTP/1.1 200 OK
[Layer 4 - Transport] TCP connection closed (FIN -> FIN-ACK)
TCP vs UDP — Choosing the Right Delivery Guarantee
TCP (Transmission Control Protocol) and UDP (User Datagram Protocol) are the two workhorses of the Transport layer, and choosing between them is one of the most consequential decisions in system design.
TCP is like sending a package with signature confirmation. Before any data moves, there's a 3-way handshake (SYN, SYN-ACK, ACK). Every packet is numbered, acknowledged, and retransmitted if lost. Order is guaranteed. This reliability costs time — that handshake adds latency, and the acknowledgment mechanism adds overhead.
UDP is like dropping a flyer through every door in the neighbourhood. You send it and forget it. No handshake, no acknowledgment, no guarantee of delivery or order. But it's blazingly fast, which is exactly what you need for real-time applications.
Here's the mental model that sticks: would you rather watch a video call with a tiny delay but perfect clarity, or have it pause every time a packet gets retransmitted? Video calling uses UDP because a dropped frame is better than a frozen screen. Your bank transfer uses TCP because a dropped byte means someone loses money.
In modern systems, QUIC (used by HTTP/3) is effectively UDP with reliability built on top of it — proof that the TCP/UDP choice isn't always binary.
import socket import threading import time # ───────────────────────────────────────────── # TCP SERVER: Reliable, connection-oriented # Use when: data integrity matters (APIs, file transfer, chat messages) # ───────────────────────────────────────────── def run_tcp_server(host='127.0.0.1', port=9001): tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) tcp_server.bind((host, port)) tcp_server.listen(1) # Queue up to 1 connection print(f"[TCP Server] Listening on {host}:{port}") client_socket, client_address = tcp_server.accept() print(f"[TCP Server] Connection established with {client_address}") # TCP guarantees this message arrives in full, in order received_data = client_socket.recv(1024).decode('utf-8') print(f"[TCP Server] Received: '{received_data}'") client_socket.send(b"TCP ACK: Message received intact") client_socket.close() tcp_server.close() # ───────────────────────────────────────────── # UDP SERVER: Fast, connectionless # Use when: speed matters more than reliability (video streams, game state, DNS) # ───────────────────────────────────────────── def run_udp_server(host='127.0.0.1', port=9002): udp_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # SOCK_DGRAM = UDP udp_server.bind((host, port)) print(f"[UDP Server] Listening on {host}:{port}") # No .accept() call — UDP has no connection concept received_data, sender_address = udp_server.recvfrom(1024) print(f"[UDP Server] Datagram from {sender_address}: '{received_data.decode()}'") # UDP CAN reply, but there's no guarantee the client gets it udp_server.sendto(b"UDP reply: no delivery guarantee", sender_address) udp_server.close() def run_tcp_client(host='127.0.0.1', port=9001): time.sleep(0.1) # Give server time to start tcp_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) tcp_client.connect((host, port)) # This triggers the 3-way handshake tcp_client.send(b"Hello via TCP — this WILL arrive") response = tcp_client.recv(1024) print(f"[TCP Client] Server replied: '{response.decode()}'") tcp_client.close() def run_udp_client(host='127.0.0.1', port=9002): time.sleep(0.1) # Give server time to start udp_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # No .connect() required — we just fire and hope udp_client.sendto(b"Hello via UDP — no handshake needed", (host, port)) response, _ = udp_client.recvfrom(1024) print(f"[UDP Client] Server replied: '{response.decode()}'") udp_client.close() # Run both demos side by side tcp_server_thread = threading.Thread(target=run_tcp_server, daemon=True) udp_server_thread = threading.Thread(target=run_udp_server, daemon=True) tcp_server_thread.start() udp_server_thread.start() run_tcp_client() run_udp_client() tcp_server_thread.join(timeout=2) udp_server_thread.join(timeout=2) print("\nKey difference: TCP needed a handshake. UDP just sent.")
[UDP Server] Listening on 127.0.0.1:9002
[TCP Server] Connection established with ('127.0.0.1', 54832)
[TCP Server] Received: 'Hello via TCP — this WILL arrive'
[TCP Client] Server replied: 'TCP ACK: Message received intact'
[UDP Server] Datagram from ('127.0.0.1', 61204): 'Hello via UDP — no handshake needed'
[UDP Client] Server replied: 'UDP reply: no delivery guarantee'
Key difference: TCP needed a handshake. UDP just sent.
DNS Deep Dive — What Actually Happens When You Type a URL
DNS (Domain Name System) is the internet's phonebook. You know the name (google.com), and DNS finds the phone number (IP address). But the process behind that lookup is more fascinating than most people realise — and it's a classic interview question.
When your browser needs to resolve 'api.github.com', it doesn't just ask one server. It walks a hierarchy. First, it checks its local cache. If that's empty, it asks your OS's resolver. If that misses, it queries your ISP's recursive resolver. That resolver then walks the DNS tree: it asks a Root Name Server for the authoritative server for '.com', then asks that server for 'github.com', then finally asks GitHub's authoritative DNS server for 'api.github.com'. The answer comes back and gets cached at every step.
This hierarchy is why DNS is both fast (caching) and fault-tolerant (multiple root servers). There are only 13 root DNS server addresses (A through M) but hundreds of physical machines behind them using Anycast routing.
TTL (Time To Live) is the underrated hero here. It controls how long a DNS answer is cached. A TTL of 300 means the record is cached for 5 minutes. When you're doing a zero-downtime deployment and changing your server's IP, you lower the TTL hours before the switch — so the old record expires quickly after cutover. That's operational knowledge that impresses interviewers.
import socket import dns.resolver # pip install dnspython import time # This script traces DNS resolution for a domain # and shows TTL values — the caching mechanism that makes DNS scalable def trace_dns_resolution(domain_name: str): print(f"\n{'='*55}") print(f" DNS Resolution Trace for: {domain_name}") print(f"{'='*55}") # Step 1: Check what the OS resolver returns # This uses your system's /etc/resolv.conf or Windows DNS settings start_time = time.time() os_resolved_ip = socket.gethostbyname(domain_name) resolution_time_ms = (time.time() - start_time) * 1000 print(f"\n[OS Resolver]") print(f" Resolved IP : {os_resolved_ip}") print(f" Time taken : {resolution_time_ms:.2f}ms (includes cache check)") # Step 2: Use dnspython to get detailed DNS record information # This bypasses the OS cache and queries the resolver directly resolver = dns.resolver.Resolver() resolver.nameservers = ['8.8.8.8'] # Use Google's public DNS print(f"\n[A Record Query via 8.8.8.8 (Google DNS)]") try: answer = resolver.resolve(domain_name, 'A') # 'A' record = IPv4 address for record in answer: print(f" IP Address : {record.address}") print(f" TTL : {answer.ttl} seconds") print(f" Meaning : This record is cached for {answer.ttl // 60} min {answer.ttl % 60} sec") except dns.resolver.NXDOMAIN: print(f" Domain '{domain_name}' does not exist in DNS.") # Step 3: Query for CNAME records (common for CDNs like CloudFront) print(f"\n[CNAME Record Query — used by CDNs and load balancers]") try: cname_answer = resolver.resolve(domain_name, 'CNAME') for record in cname_answer: print(f" CNAME Target : {record.target}") print(f" (This domain is an alias — follow the chain to get the real IP)") except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN): print(f" No CNAME record — this domain maps directly to an IP.") # Step 4: Query MX records (email routing) print(f"\n[MX Record Query — controls where email is delivered]") try: mx_answer = resolver.resolve(domain_name, 'MX') for record in mx_answer: print(f" Mail Server : {record.exchange} (priority: {record.preference})") except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN): print(f" No MX records found.") print(f"\n[Summary]") print(f" Domain : {domain_name}") print(f" Final IP: {os_resolved_ip}") print(f" Tip : Lower TTL before changing IPs. Higher TTL = faster performance.") # Run the trace on a well-known domain trace_dns_resolution("github.com")
DNS Resolution Trace for: github.com
=======================================================
[OS Resolver]
Resolved IP : 140.82.121.4
Time taken : 12.34ms (includes cache check)
[A Record Query via 8.8.8.8 (Google DNS)]
IP Address : 140.82.121.4
TTL : 60 seconds
Meaning : This record is cached for 1 min 0 sec
[CNAME Record Query — used by CDNs and load balancers]
No CNAME record — this domain maps directly to an IP.
[MX Record Query — controls where email is delivered]
Mail Server : aspmx.l.google.com. (priority: 1)
Mail Server : alt1.aspmx.l.google.com. (priority: 5)
[Summary]
Domain : github.com
Final IP: 140.82.121.4
Tip : Lower TTL before changing IPs. Higher TTL = faster performance.
HTTP vs HTTPS, Status Codes, and Subnetting — The Interview Essentials
These three topics appear in virtually every networking interview, so let's cover them with precision.
HTTP vs HTTPS: HTTP sends everything in plaintext. If you're on a coffee shop Wi-Fi, anyone on that network with Wireshark can read your HTTP traffic. HTTPS wraps HTTP inside TLS (Transport Layer Security), which provides encryption, authentication (via certificates), and integrity checking. The TLS handshake happens after the TCP handshake — the server sends its certificate, the client verifies it against trusted Certificate Authorities, and they negotiate a symmetric encryption key. After that, all data is encrypted.
HTTP Status Codes: These are a language. 2xx means success. 3xx means redirect. 4xx means the client made an error. 5xx means the server failed. The distinction between 4xx and 5xx is critical in SLAs — a 429 (rate limit) is the client's fault, a 503 (service unavailable) is yours.
Subnetting: An IP address like 192.168.1.100/24 means the first 24 bits identify the network and the last 8 bits identify the host. /24 gives you 256 addresses (254 usable). /16 gives you 65,536. You subnet to isolate traffic, enforce security boundaries, and manage IP allocation. AWS VPCs, Kubernetes pod networks, and Docker bridge networks all use subnetting concepts daily.
import ipaddress import http.client import ssl # ───────────────────────────────────────────────────── # PART 1: Subnet Calculator # Understanding subnets is crucial for cloud and network interviews # ───────────────────────────────────────────────────── def explain_subnet(cidr_notation: str): print(f"\n{'='*50}") print(f" Subnet Analysis: {cidr_notation}") print(f"{'='*50}") network = ipaddress.IPv4Network(cidr_notation, strict=False) print(f" Network Address : {network.network_address}") print(f" Broadcast Address: {network.broadcast_address}") print(f" Subnet Mask : {network.netmask}") print(f" Prefix Length : /{network.prefixlen}") print(f" Total Hosts : {network.num_addresses}") print(f" Usable Hosts : {network.num_addresses - 2} (subtract network & broadcast)") # Show the first 3 and last usable host addresses all_hosts = list(network.hosts()) # .hosts() excludes network and broadcast if len(all_hosts) >= 3: print(f" First Usable IP : {all_hosts[0]}") print(f" Second Usable IP : {all_hosts[1]}") print(f" Last Usable IP : {all_hosts[-1]}") # Check if specific IPs are in this subnet test_ip = ipaddress.IPv4Address('192.168.1.150') print(f" Is {test_ip} in subnet? {test_ip in network}") # ───────────────────────────────────────────────────── # PART 2: HTTP Status Code Classifier # Know the MEANING, not just the number # ───────────────────────────────────────────────────── def classify_http_status(status_code: int) -> str: status_families = { 1: "1xx Informational — request received, processing continues", 2: "2xx Success — request successfully received, understood, and accepted", 3: "3xx Redirection — further action needed to complete the request", 4: "4xx Client Error — the REQUEST has bad syntax or cannot be fulfilled", 5: "5xx Server Error — the SERVER failed to fulfil a valid request" } family = status_code // 100 return status_families.get(family, "Unknown status code family") def demonstrate_http_vs_https(): print(f"\n{'='*50}") print(" HTTP vs HTTPS Connection Demo") print(f"{'='*50}") # HTTP connection — plaintext, no encryption print("\n[HTTP - Port 80, NO encryption]") http_conn = http.client.HTTPConnection("example.com", timeout=5) http_conn.request("HEAD", "/") # HEAD request: only headers, no body http_response = http_conn.getresponse() print(f" Status: {http_response.status} — {classify_http_status(http_response.status)}") print(f" Server: {http_response.getheader('Server', 'Not disclosed')}") print(f" ⚠️ All data transmitted in PLAINTEXT — visible to anyone sniffing the network") http_conn.close() # HTTPS connection — encrypted with TLS print("\n[HTTPS - Port 443, TLS encrypted]") # ssl.create_default_context() loads trusted Certificate Authorities tls_context = ssl.create_default_context() https_conn = http.client.HTTPSConnection("example.com", context=tls_context, timeout=5) https_conn.request("HEAD", "/") https_response = https_conn.getresponse() print(f" Status: {https_response.status} — {classify_http_status(https_response.status)}") print(f" TLS Version: {https_conn.sock.version()}") print(f" Cipher Suite: {https_conn.sock.cipher()[0]}") print(f" ✅ All data encrypted — intercepted traffic is gibberish without the key") https_conn.close() # Run all demos explain_subnet("192.168.1.0/24") explain_subnet("10.0.0.0/16") print(f"\n{'='*50}") print(" HTTP Status Code Reference") print(f"{'='*50}") for code in [200, 201, 301, 304, 400, 401, 403, 404, 429, 500, 502, 503]: print(f" {code}: {classify_http_status(code).split('—')[0].strip()}") demonstrate_http_vs_https()
Subnet Analysis: 192.168.1.0/24
==================================================
Network Address : 192.168.1.0
Broadcast Address: 192.168.1.255
Subnet Mask : 255.255.255.0
Prefix Length : /24
Total Hosts : 256
Usable Hosts : 254 (subtract network & broadcast)
First Usable IP : 192.168.1.1
Second Usable IP : 192.168.1.2
Last Usable IP : 192.168.1.254
Is 192.168.1.150 in subnet? True
==================================================
Subnet Analysis: 10.0.0.0/16
==================================================
Network Address : 10.0.0.0
Broadcast Address: 10.0.255.255
Subnet Mask : 255.255.0.0
Prefix Length : /16
Total Hosts : 65536
Usable Hosts : 65534 (subtract network & broadcast)
First Usable IP : 10.0.0.1
Second Usable IP : 10.0.0.2
Last Usable IP : 10.0.255.254
Is 192.168.1.150 in subnet? False
==================================================
HTTP Status Code Reference
==================================================
200: 2xx Success
201: 2xx Success
301: 3xx Redirection
304: 3xx Redirection
400: 4xx Client Error
401: 4xx Client Error
403: 4xx Client Error
404: 4xx Client Error
429: 4xx Client Error
500: 5xx Server Error
502: 5xx Server Error
503: 5xx Server Error
==================================================
HTTP vs HTTPS Connection Demo
==================================================
[HTTP - Port 80, NO encryption]
Status: 200 — 2xx Success
Server: ECS (dcb/7F83)
⚠️ All data transmitted in PLAINTEXT — visible to anyone sniffing the network
[HTTPS - Port 443, TLS encrypted]
Status: 200 — 2xx Success
TLS Version: TLSv1.3
Cipher Suite: TLS_AES_256_GCM_SHA384
✅ All data encrypted — intercepted traffic is gibberish without the key
| Aspect | TCP | UDP |
|---|---|---|
| Connection | Connection-oriented (3-way handshake) | Connectionless (no handshake) |
| Reliability | Guaranteed delivery & ordering | No delivery guarantee, no ordering |
| Speed | Slower (overhead from ACKs) | Faster (fire and forget) |
| Error Checking | Full — retransmits lost packets | Checksum only — no retransmission |
| Use Cases | HTTP, HTTPS, SSH, FTP, SMTP | DNS, video streaming, VoIP, gaming |
| Header Size | 20–60 bytes | 8 bytes fixed |
| Flow Control | Yes (sliding window) | No |
| Congestion Control | Yes (slow start, AIMD) | No — app must handle it |
| HTTP Version | HTTP/1.1, HTTP/2 | HTTP/3 (via QUIC) |
🎯 Key Takeaways
Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.