1. IP Addressing, DNS Resolution & Load Balancers
Every machine reachable over a network needs an IP address — a
numeric identifier (IPv4's familiar 192.0.2.1 or IPv6's longer
hexadecimal form) that routers use to move packets from a client to the right host.
You don't need to design routing protocols for a system design interview, but you do
need the one-sentence version: humans type domain names, and something has to
translate a domain name into an IP address before a single packet can be sent. That
something is DNS, the Domain Name System.
DNS resolution is a hierarchy, not a single lookup. A browser asks a recursive
resolver (often run by your ISP or a public service) for api.example.com;
the resolver walks from a root server, to the .com TLD server, to
example.com's authoritative nameserver, which finally returns an IP
address. In practice almost every one of those lookups is served from a cache — DNS
records carry a TTL (time-to-live), and resolvers cache the answer for
that long, which is why DNS lookups are usually microseconds, not multiple network
round trips.
1. Client asks DNS: "what is the IP for api.example.com?"
2. DNS resolver returns a cached or freshly resolved IP
(often the IP of a load balancer, not any single app server)
3. Client opens a TCP/TLS connection to that IP
4. Load balancer receives the request and forwards it to
one healthy app server behind it
5. App server processes the request and returns a response
back through the load balancer to the client
That "IP of a load balancer, not any single app server" detail in step 2 is the key idea for this section. Once a system needs more than one app server — which is nearly every real system — something has to decide which server handles each incoming request, and hide the fact that there are multiple servers at all behind one stable address. That's the load balancer's job. Conceptually it sits between clients and a pool of app servers, continuously health-checks that pool (removing a server that stops responding), and distributes requests across whichever servers are healthy. Load balancers come in two broad flavors: Layer 4 (transport layer, routing based on IP and TCP/UDP port, fast but blind to request content) and Layer 7 (application layer, can read HTTP headers, paths and cookies to make smarter routing decisions, at the cost of more per-request overhead). Week 3 covers the actual algorithms — round robin, least connections, consistent hashing — a load balancer uses to pick which server gets a given request.
A low TTL means resolvers re-check frequently, so failing over to a new IP (say, during an outage) propagates fast — but it also means every resolver hits your DNS provider more often. A high TTL reduces DNS load but means a failover can take minutes to reach every client whose resolver is still holding a stale cached answer. This is the same "how long can this response be stale" question you'll see again with cache TTLs in Weeks 6–7 — it just shows up first at the DNS layer.
2. REST vs. gRPC vs. GraphQL
Once a request reaches an app server, the shape of that request and response is an API design choice, and the three dominant styles trade off differently enough that picking one is a real system design decision, not a matter of taste.
REST models a system as resources, addressed by URLs, manipulated with
HTTP verbs (GET, POST, PUT, DELETE)
and typically carrying JSON payloads. It's stateless, human-readable, cacheable using
standard HTTP semantics (an intermediary can cache a GET response by URL
without understanding your business logic), and universally supported — which is why
it's the default choice for public-facing APIs consumed by clients you don't control.
Its weakness is chattiness: a mobile client that needs a user's profile, their recent
posts, and their follower count might need three separate REST calls, or one bloated
endpoint that over-fetches data most callers don't need.
gRPC runs over HTTP/2, serializes messages as compact binary protocol
buffers instead of text-based JSON, and is defined by a strict schema (a
.proto file) shared between client and server. That combination makes it
significantly faster to serialize, deserialize and transmit than JSON-over-HTTP/1.1,
and it natively supports streaming (a client or server — or both — can keep a
connection open and stream messages rather than one request, one response). The cost
is flexibility: both sides need the generated client/server code from the same schema,
and it's far less friendly to a browser or a public developer consuming your API ad
hoc. This profile is exactly why gRPC has become the default for
internal service-to-service communication in a microservices
architecture, where you control both ends and low latency compounds across many
internal hops.
GraphQL exposes a single endpoint and lets the client specify exactly which fields it wants in a query, resolved from possibly many underlying data sources on the server. That directly solves REST's over-fetching/under-fetching problem — the mobile client from the earlier example can ask for profile, posts and follower count in one round trip, naming only the fields it needs. The tradeoff moves complexity from the client to the server: caching is much harder (there's no stable URL per resource to cache against), and a poorly bounded query can force the server to do far more work than its author expected, which is why production GraphQL services need query complexity limits and depth limiting.
REST (2 calls, fixed shape per endpoint):
GET /users/42 -> { "id": 42, "name": "Ana", "bio": "..." }
GET /users/42/posts -> [ { "id": 901, "title": "..." }, ... ]
GraphQL (1 call, client-chosen shape):
query {
user(id: 42) {
name
posts(limit: 5) { title }
}
}
gRPC (schema-defined, binary over HTTP/2):
service UserService {
rpc GetUser (UserRequest) returns (UserResponse);
rpc StreamUserPosts (UserRequest) returns (stream Post);
}
A strong interview answer rarely picks one API style for an entire system — it picks per boundary. A common, defensible combination: REST or GraphQL at the public edge (broad compatibility, or client-driven data shaping for a mobile app with limited bandwidth), and gRPC between your own internal services (lowest latency, strict contracts, streaming support). Naming that split shows you understand where each style's tradeoffs actually pay off.
3. CDNs & Edge Caching
A CDN (Content Delivery Network) is a geographically distributed network of edge servers — often called Points of Presence, or PoPs — that cache and serve content physically closer to users than your origin servers are. Recall Week 1's latency numbers: a round trip to a different continent costs roughly 100–150ms, while a round trip within the same data center costs roughly 0.5ms. A CDN's entire value proposition is collapsing that gap by answering the request from a PoP a few hundred kilometers from the user instead of from an origin server on another continent.
For static content — images, videos, JS/CSS bundles, fonts — this is
close to a pure win. Static assets don't change per request, so they're given long
cache TTLs (often measured in days or months) and are typically served with a
content-hashed filename (app.a3f21c.js) so that deploying a new version
means shipping a new URL, not invalidating a cached one — sidestepping the
invalidation problem entirely rather than solving it.
Dynamic content — a personalized API response, a search result — can't simply be cached with a long TTL because it differs per user or per request. CDNs still help here in two ways: some dynamic responses are cacheable for short windows (a product listing page that's identical for every visitor for 30 seconds is a reasonable CDN cache candidate), and even fully uncacheable requests benefit from edge termination — the CDN terminates the client's TCP/TLS connection at the nearby PoP, then reuses an already-open, low-latency connection to the origin over the CDN's own backbone network. The user's slow, high-latency leg of the journey gets shortened even when the origin still has to do the real work.
Static asset, cache hit:
Client --> Nearest CDN PoP --> (cached copy) --> response
(origin server never contacted)
Static asset, cache miss:
Client --> Nearest CDN PoP --> Origin server --> PoP caches
response --> response returned, later requests are hits
Dynamic, uncacheable request:
Client --> Nearest CDN PoP (TLS terminated here)
--> CDN backbone --> Origin server --> response
(client's slow leg is short; PoP-to-origin leg is fast and reused)
A CDN is worth thinking of as a cache-aside cache that happens to live at the network edge instead of inside your data center: hits skip the origin, misses populate the cache for next time, and everything you'll learn about TTLs and eviction in Weeks 6–7 for Redis or Memcached applies conceptually here too. The difference is placement, not the underlying idea.
4. Hands-on Exercise
Design the networking & API layer for a recipe-sharing app
Users browse recipes (with photos), search by ingredient, and a small internal "recommendation" service suggests recipes to a "feed" service. Apply this week's material to decide how requests actually move through this system.
Requirements:
- Sketch the request path for a user loading the app: client, DNS, load balancer, app servers — label each hop.
- Choose an API style (REST, gRPC or GraphQL) for the public client-facing API and justify it in 2–3 sentences based on this week's tradeoffs, not preference.
- Choose an API style for the internal call between the recommendation service and the feed service, and explain why your answer differs (or doesn't) from question 2.
- Identify which assets in this app are good CDN candidates for long-TTL static caching, and which requests are dynamic and would rely on edge termination instead.
- Write one sentence on what could go wrong if recipe photo URLs were reused after a user re-uploads a corrected photo, connecting it back to Section 3's content-hashed filename idea.
For question 3, think about what the recommendation-to-feed call actually needs: is it public, does it need broad client compatibility, and how many times per second might it be called internally compared to a single user's page load? Internal, high-frequency, schema-controlled calls are the profile this week associates with one specific API style.
5. Knowledge Check
Four quick questions. Expand each to check your answer.
Q1
Why does a system with multiple app servers need a load balancer instead of just handing clients a list of server IPs directly?
Why does a system with multiple app servers need a load balancer instead of just handing clients a list of server IPs directly?
Clients holding a list of raw server IPs would need to know, individually, which servers are healthy, how loaded each one is, and would need updating every time a server was added, removed or replaced — none of which a client can reasonably track. A load balancer exposes one stable address, centralizes health checking, and distributes load using an algorithm the client never has to know about, which is also what makes it possible to add or remove capacity without any client-side change.
Q2
An internal microservice needs to call another internal microservice thousands of times per second with strict latency requirements. Why would gRPC likely beat REST here?
An internal microservice needs to call another internal microservice thousands of times per second with strict latency requirements. Why would gRPC likely beat REST here?
Both endpoints are controlled by the same team, so gRPC's requirement that both sides share a schema isn't a real cost here, while its binary protobuf payloads and HTTP/2 transport serialize and transmit meaningfully faster than JSON-over-HTTP/1.1 — a difference that compounds at thousands of calls per second. REST's main advantages (broad compatibility with clients you don't control, human-readable payloads) aren't relevant for an internal, high-frequency, schema-controlled call.
Q3
What specific problem does GraphQL solve that plain REST endpoints struggle with, and what does that solution cost you?
What specific problem does GraphQL solve that plain REST endpoints struggle with, and what does that solution cost you?
GraphQL solves over-fetching and under-fetching: a client can request exactly the fields it needs across what would otherwise be several REST calls, in a single round trip. The cost is that HTTP-level caching by URL no longer works cleanly since every query can shape a different response from the same endpoint, and an unbounded query can force the server to do far more resolution work than expected, requiring explicit complexity or depth limits.
Q4
A response is fully personalized and can never be cached. Why might routing it through a CDN still reduce latency?
A response is fully personalized and can never be cached. Why might routing it through a CDN still reduce latency?
Even without caching the response, the CDN's nearby PoP can terminate the client's slow, high-latency TCP/TLS connection close to the user, then forward the request to the origin over the CDN's own fast, already-open backbone connections. This shortens the leg of the trip that was actually slow (the client's long-distance hop) while the origin still computes the personalized response, which is why edge termination helps even fully dynamic, uncacheable traffic.