Home CS Fundamentals Computer Networks Interview Questions Explained — TCP, DNS, HTTP and Beyond

Computer Networks Interview Questions Explained — TCP, DNS, HTTP and Beyond

In Plain English 🔥
Imagine the internet is a global postal system. Your computer is a house with an address (IP address), the postal routes are the network cables and Wi-Fi signals, and the rules about how letters get packed, addressed, and delivered are the protocols. When you visit google.com, you're essentially writing a letter, dropping it in a mailbox, watching it get sorted through multiple post offices (routers), and getting a reply back — all in milliseconds. Computer networking is the science of making that postal system fast, reliable, and secure.
⚡ Quick Answer
Imagine the internet is a global postal system. Your computer is a house with an address (IP address), the postal routes are the network cables and Wi-Fi signals, and the rules about how letters get packed, addressed, and delivered are the protocols. When you visit google.com, you're essentially writing a letter, dropping it in a mailbox, watching it get sorted through multiple post offices (routers), and getting a reply back — all in milliseconds. Computer networking is the science of making that postal system fast, reliable, and secure.

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.

osi_layer_demo.py · PYTHON
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
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()
▶ Output
[Layer 3 - Network] Resolved 'example.com' -> IP: 93.184.216.34
[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)
⚠️
Interview Gold:When asked about OSI layers, anchor your answer in debugging. Say: 'If ping works but HTTP doesn't, the issue is Layer 7, not Layer 3.' That shows you understand the model operationally, not just academically.

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.

tcp_vs_udp_server_demo.py · PYTHON
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
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.")
▶ Output
[TCP Server] Listening on 127.0.0.1:9001
[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.
🔥
Real-World Mapping:DNS uses UDP for queries (fast, small payloads) but falls back to TCP when the response is too large (>512 bytes). HTTP/1.1 and HTTP/2 use TCP. HTTP/3 uses QUIC (UDP-based). Knowing these specifics in an interview is a strong signal.

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.

dns_resolution_trace.py · PYTHON
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
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")
▶ Output
=======================================================
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.
⚠️
Watch Out:Never change a DNS record without first lowering the TTL to 60–300 seconds at least 24 hours in advance. If you change an IP with a TTL of 86400 (24 hours), old clients will keep hitting the wrong server for an entire day — and you can't force them to flush their cache.

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.

networking_essentials_demo.py · PYTHON
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
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()
▶ Output
==================================================
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
⚠️
Pro Tip:When asked 'what's the difference between 401 and 403?', say: '401 means unauthenticated — we don't know *who* you are. 403 means unauthorised — we know exactly who you are, you just don't have permission.' That distinction shows you think about security design, not just HTTP syntax.
AspectTCPUDP
ConnectionConnection-oriented (3-way handshake)Connectionless (no handshake)
ReliabilityGuaranteed delivery & orderingNo delivery guarantee, no ordering
SpeedSlower (overhead from ACKs)Faster (fire and forget)
Error CheckingFull — retransmits lost packetsChecksum only — no retransmission
Use CasesHTTP, HTTPS, SSH, FTP, SMTPDNS, video streaming, VoIP, gaming
Header Size20–60 bytes8 bytes fixed
Flow ControlYes (sliding window)No
Congestion ControlYes (slow start, AIMD)No — app must handle it
HTTP VersionHTTP/1.1, HTTP/2HTTP/3 (via QUIC)

🎯 Key Takeaways

    🔥
    TheCodeForge Editorial Team Verified Author

    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.

    ← PreviousVPN ExplainedNext →CDN How It Works
    Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged