System Design · Module 1 · Lesson 1 of 4 · 30 min
What System Design Is and What You Must Explain
Learn what a design answer contains, distinguish high-level from low-level design, and connect every box to a real request.
01 The idea
The problem this lesson solves
You build a short-link program on your laptop. It accepts a destination, stores it under a code such as a7, and redirects a reader who opens /r/a7. The program works during your demonstration. Now close it and reopen it: are the links still there? Run two copies: do both know the same links? Unplug the database during a request: does the reader receive an honest error? System design begins with these questions because users depend on the behavior of the whole service, not just the function that ran successfully once.
You need basic programming and arithmetic. This module introduces the design vocabulary; the next module develops HTTP step by step.
02 Worked example
Follow one short-link request
03 The method
Understand the mechanism
From a function to a service
A function receives arguments, performs work and returns a result. A service exposes operations to other programs, often over a network. Its callers do not share its memory or execution clock. A request can arrive twice, arrive late, or finish on the server after the caller has stopped waiting. This changes the problem even when the business logic is only a few lines long.
Consider a function redirect(code). Inside one process, it can read a dictionary and return a URL. Behind an HTTP endpoint, the implementation must also decode the path, decide whether the caller can use the operation, find the correct stored record, choose a status code and deliver a response. Deployment introduces more questions: which process receives the request, where records survive a restart, and what happens when a dependency fails?
The purpose of a design is to make those responsibilities explicit. You do not need a separate service for every responsibility. A single application process can validate requests, apply business rules and format responses. Separating responsibilities in your explanation is a reasoning tool; separating deployments is an additional decision with operational costs.
Give every piece of state an owner
State is information that can affect a later operation. For the short-link service, the mapping a7 → https://example.com/guide is durable business state: future readers depend on it. A request-local variable holding a7 is temporary state. A cached copy of the mapping is replaceable state if the authoritative record remains elsewhere.
An owner is the component whose decision the rest of the system treats as authoritative. If the database owns the mapping, the application must not invent a new destination when the database is unavailable. If a cache stores a copy, deleting the cache should reduce performance rather than erase the only record of the link.
This distinction explains why adding app servers can break an apparently working program. If A stores links only in its own dictionary, B cannot answer requests for those links. Sticky routing might hide the problem until A restarts. Moving the authoritative mapping to shared durable storage addresses ownership; adding a load balancer by itself does not.
Durability also has a failure boundary. A file on one machine may survive a process restart and still disappear when the disk is lost. A design must say which failures an acknowledged save is expected to survive, then connect that promise to persistence, replication and recovery mechanisms.
Trace the operation in both directions
Draw client → application → database for a redirect. The outgoing arrows should say GET /r/a7 and lookup mapping where code = a7. The return path carries either a destination, a confirmed absence, or a failure to determine the result. These are different outcomes, even if the application represents all three badly as null.
If the database returns the destination, the application can send a temporary redirect with a Location header and an explicit caching policy. If a successful lookup finds no record, the endpoint can return its documented missing response. If the query times out, the application does not know whether the code exists. Returning 404 would make an unavailable dependency look like valid business data.
Trace creation separately. The client submits a destination, the app validates it, a code is allocated, and the mapping is committed. Only then can the server claim that the link has been saved under the stated durability contract. If the response is lost after commit, the caller faces an ambiguous outcome. The later idempotency lesson handles this case; recognizing it here is already useful.
Notice what the diagram reveals: a request can fail before validation, during storage, or while returning a result. “The server failed” is too vague to determine whether retrying is safe.
Distinguish architecture from object design
High-level design describes components, interfaces, data placement and behavior across failures. For our service, it explains why requests go to a stateless application, where mappings live and how a redirect is served. Low-level design describes how one component is implemented: a LinkRepository interface, a URL validator, a code generator, and error types that distinguish missing records from failed queries.
The same requirement appears at both levels. High-level design says code allocation must avoid duplicate ownership. Low-level design specifies the repository operation that attempts a unique insert and reports a collision. Working code then implements that operation against the selected database.
Do not classify a prompt from its noun alone. A “rate limiter” can mean implementing a token-bucket class or designing a shared enforcement service used by thousands of app instances. Establish the expected artifact and failure scope. A parking system can involve local allocation rules, a distributed reservation service, or both.
An implementation is incomplete when its architecture assumes a guarantee that its code does not enforce. A class diagram is incomplete when it hides remote calls behind methods that appear instantaneous and infallible. Connecting the levels prevents both mistakes.
Measure the constraint before adding a component
Suppose the first deployment handles 3 requests per second and database lookups take 4 ms under that load. A cache might save little while introducing expiration, invalidation and cold-start behavior. At a much higher workload, the same database may become the limiting resource. The right design depends on the measured constraint and required service quality.
A bottleneck is the resource that limits useful throughput or causes the target latency to be missed. It might be CPU, disk I/O, a connection pool, one locked row, network bandwidth or a remote dependency. Adding app servers helps only if app capacity is the limiting factor and the downstream system can absorb their combined work.
Every extra component adds a benefit and an obligation. A queue can absorb a burst, but accepted work needs storage, retry rules, capacity and a completion contract. A replica can serve suitable reads, but those reads may lag. A cache can reduce repeated work, but it introduces another representation of state.
Write a decision as a causal statement: “We add a cache because repeated public redirects exceed the tested database read budget; cached destinations may be at most thirty seconds old under the chosen policy.” That statement is more useful than a list of technologies because it can be tested and revised.
Define the smallest complete design
A useful starting design for this exercise contains an HTTP application, a relational database with a unique code key, and a deployment that can be monitored and recovered. It also includes the behaviors often omitted from drawings: bounded request timeouts, validation, authentication for management operations, structured errors and a backup-restore procedure.
This starting point does not promise unlimited availability or traffic. If the only app instance fails, requests stop until it recovers or is replaced. If the database is unavailable, redirects that require it cannot succeed. Those limits are acceptable only if they fit the stated objectives. Explaining them is part of completeness.
You improve the design by changing a concrete assumption. If one app loss must be tolerated, deploy enough independently replaceable instances and route around an unhealthy one. If acknowledged data must survive a machine loss, investigate the database persistence and replication contract. If restore must finish within an hour, test restore duration with representative data.
The result is a chain of reasoning: a user action creates a requirement; that requirement creates an ownership or capacity obligation; a mechanism satisfies that obligation within a stated boundary. A reviewer should be able to follow that chain without guessing what a box is supposed to guarantee.
Guided tutorial
Write the redirect contract
Create a three-row outcome table for GET /r/a7. Found: return a redirect with the stored destination in Location. Missing after a successful authoritative lookup: return the documented missing response. Database unavailable: return a service error. Add a note that no branch invents a destination. This is the contract you will test against the design.
Implement the design on paper
Use one row, code=a7 and destination=https://example.com/guide. Draw the browser, application and database. Label the request to the database with a7 and its result with the destination. Draw the response back through the application. Then cross out the database and walk the same arrows: the application can still validate the request, but cannot complete the lookup.
Test a second application
Add app B and send the first request to A and the second to B. If the mapping was only in A’s memory, explain why B fails. Replace the private dictionary with the shared authoritative table and repeat the trace. B can now answer, provided it can reach the database; this does not make the database itself redundant.
Produce a reviewable result
Write two decisions and two limits. Example decision: the database owns mappings so app replacement does not lose links. Example limit: a database outage prevents uncached redirects. Hand the trace to another reader and ask them to identify the response for found, missing and unavailable without asking you what an arrow means.
Request trace
client → GET /r/a7 → application
application → find_by_code("a7") → database
database → destination or missing → application
application → redirect or documented error → clientCheck your result
Your trace names the mapping’s owner and distinguishes a missing code from a failed database lookup. A timeout must not become a false 404.
04 Cheat sheet
Review the decisions
Use this table to explain the mechanism and its limit without memorizing a product name.
| Choice or concept | Remember | Check |
|---|---|---|
| Client | Requests a redirect | Does not decide whether a code exists |
| Application | Validates and responds | Must distinguish missing from unavailable |
| Database | Persists mappings | Needs a recovery and capacity plan |
05 Where & why
Check the primary references
Use official references for protocol and implementation details. The questions here are teaching prompts.
06 Interview questions
Explain the reasoning out loud
Answer before opening the explanation. The questions progress from vocabulary to decisions and failure behavior.
What does Component mean?
What does HLD mean?
What does LLD mean?
What makes a box-and-arrow drawing a design?
Why not choose the database first?
Can a small design be a good answer?
What is the first failure to trace?
How do you choose the level of detail?
07 Practice problems
Practice a new case
Solve the new cases before revealing hints. Complete worked solutions follow the set.
Identify responsibility
EasyShow the hint
Name the artifact
EasyShow the hint
Trace absence
MediumShow the hint
Remove a box
MediumShow the hint
Describe the arrows
MediumShow the hint
Defend a minimal design
HardShow the hint
Worked solutions
1. Identify responsibility
The client collects input; the API validates and authorizes; the database durably stores the note. The API must derive identity from authentication rather than trust an owner supplied by the client.
2. Name the artifact
One controller needs states, events and interfaces. A fleet service needs device identity, APIs, telemetry storage and behavior when devices disconnect.
3. Trace absence
A timeout does not prove absence. Return a documented unavailable response with a bounded retry policy; report missing only when an authoritative lookup succeeds and finds no accessible record.
4. Remove a box
The rate alone does not justify a cache. Measure latency and database capacity first; a cache introduces freshness, memory and recovery obligations.
5. Describe the arrows
The client sends image id p1 to the API; the API requests p1 metadata from the store; the store returns metadata or absence; the API returns the authorized representation or documented error.
6. Defend a minimal design
Use a client, an authenticated API and durable message storage. State ordering, retention and posting permissions. Initial limits might be database read capacity and one-instance availability; add complexity only when the targets require it.