What System Design Is and What You Must Explain

Requirements and the Design Framework · 30 min

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.

A useful design explains who requests what, where the authoritative state lives, and how the request can fail.

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.

ComponentA unit with a responsibility and an interface, such as an API or a database.
HLDHigh-level design: components, data flow, scale and failure behavior.
LLDLow-level design: interfaces, classes, states and implementation decisions inside a component.

02 Worked example

Follow one short-link request

What System Design Is and What You Must Explain worked diagram01ReaderGET /r/a702ApplicationValidate + route03DatabaseOwns mapping04ResponseRedirect or errorWhat System Design Is and What You Must Explain compact diagram01ReaderGET /r/a702ApplicationValidate + route03DatabaseOwns mapping04ResponseRedirect or error
The database owns the mapping. The application translates a found value, a missing value or a failed lookup into different HTTP outcomes.
1
Name the actionA reader requests /r/a7. The service must either return the stored destination or a documented missing result.
2
Assign ownershipThe app interprets HTTP; the database owns the durable code-to-destination mapping.
3
Trace successRead a7, obtain https://example.com/guide, and return a redirect with Location.
4
Trace failureA database timeout is not the same as an absent code. Return a retryable service error rather than an invented 404.

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

  1. 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.

  2. 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.

  3. 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.

  4. 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 → client

Check 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 conceptRememberCheck
ClientRequests a redirectDoes not decide whether a code exists
ApplicationValidates and respondsMust distinguish missing from unavailable
DatabasePersists mappingsNeeds 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.

Primary reference
Google SRE: service-level objectives

Read Google SRE: service-level objectives

Primary reference
HTTP semantics, methods and status codes

Read HTTP semantics, methods and status codes

State the implementation version and the workload assumptions when defending your own design.

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?
A unit with a responsibility and an interface, such as an API or a database.
What does HLD mean?
High-level design: components, data flow, scale and failure behavior.
What does LLD mean?
Low-level design: interfaces, classes, states and implementation decisions inside a component.
What makes a box-and-arrow drawing a design?
Its responsibilities and contracts explain how requirements are met. A successful request trace and a failure trace reveal whether the drawing is complete.
Why not choose the database first?
Data access, durability and scale requirements determine which database capabilities matter. Choosing a product first can make the design fit the product instead of the problem.
Can a small design be a good answer?
Yes, when it satisfies the stated requirements and honestly identifies its limits. Complexity should have a measurable reason.
What is the first failure to trace?
Choose a dependency needed by the main user action, such as the mapping store. State the timeout result and how the client can recover.
How do you choose the level of detail?
Confirm whether the requested output is a service architecture, an object model or working code. Keep the initial explanation at that level and deepen selected parts when needed.

07 Practice problems

Practice a new case

Solve the new cases before revealing hints. Complete worked solutions follow the set.

Identify responsibility

Easy
For a notes app, assign user input, authorization and durable note storage to appropriate components.
Show the hint
Follow one save action.

Name the artifact

Easy
List the expected output for designing one elevator controller versus a service managing a fleet.
Show the hint
Separate local state transitions from remote coordination.

Trace absence

Medium
Explain why a storage timeout must not automatically become a missing-note response.
Show the hint
Absence is a data result; timeout is an unknown result.

Remove a box

Medium
Decide whether a cache is necessary for a service doing 3 inexpensive reads per second.
Show the hint
Ask which requirement it improves.

Describe the arrows

Medium
Write the request and result on each arrow of a three-component image-metadata lookup.
Show the hint
Use a concrete identifier.

Defend a minimal design

Hard
Design a campus announcement service for reading and posting messages, then explain its first two limits.
Show the hint
Separate the posting permission from public reading.

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.