Module 01 · Lesson 1 of 14Free
Introduction to system design
- Level
- Beginner
- Topic
- System design fundamentals
- Reading time
- 13 min
When you finish this page you can
- Turn a feature and its constraints into components, data flow and explainable decisions.
- Trace a product-page request through a small system.
- Explain how a slow dependency affects customers and changes a design.
- Identify evidence that justifies adding complexity.
Video lesson · coming soon
At the start of a festival sale, receives 1,000 product-page requests a second. Its one app server has 8 CPU cores. Now imagine its pricing service stops replying, leaving customers staring at a spinner.
Would a bigger app server fix that?
The answer depends on how the parts work together. How do you turn “show a product page” into a design you can explain, operate and change?
A feature needs a working path
For this lesson, Kartly’s product page must show a product description and a current price. The app reads product details from PostgreSQL, its database. A separate pricing service calculates the festival price.
A is a part with a clear responsibility. Here, the browser displays the page, the app assembles it, and two other components provide its data.
Kartly’s app server is healthy, but the pricing service never replies. Is the product page guaranteed to work?Show answer
Diagram not drawn yet: kartly-introduction-request-path.svg
A browser asks one app server for a product page. The app reads product details from PostgreSQL, then asks a pricing service for the current price. Both reply to the app, which returns the assembled page.
means turning a feature and its constraints into components, data flow and decisions you can explain.
A constraint is a condition the solution must respect. “Show the current price” rules out silently inventing one when pricing fails. “One app server” limits the starting deployment.
The diagram is only a map. It does not yet say what happens when a reply never comes.
Give every part a clear job
Use Kartly’s first-stage setup: one app server and one PostgreSQL database. The pricing service is an existing , not something we are designing here.
A dependency is another component your work needs in order to finish.
The app server runs Kartly’s application code. Its CPU cores execute that code; the cores are not separate services. The database keeps saved product records.
For our example page, assign responsibilities explicitly:
| Component | Responsibility | Talks to | State it keeps | If it fails |
|---|---|---|---|---|
| Browser | Display the result | App | Current screen | Customer cannot view the page |
| App server | Assemble the page | Browser, database, pricing | Data for this request | Page request fails |
| PostgreSQL | Store product details | App | Saved product records | Details unavailable |
| Pricing service | Calculate current price | App | Pricing rules | Current price unavailable |
For this example, the browser holds no authoritative product records. The app also does not keep a second permanent copy.
That makes one question easy to answer: where should a seller’s updated product description live? In PostgreSQL, not in whichever app request happened to receive it.
Clear responsibilities make changes easier to locate. Google’s SRE chapter “Simplicity” makes the broader case for components with narrow purposes and avoiding unnecessary complexity.1
This separation does not require a separate server for every responsibility. We still need to decide what belongs together.
Start with one app deployment
Imagine putting product pages, seller editing and cart handling into separate running applications immediately. Each could have its own release, configuration and network calls.
That is an option, not a starting requirement.
Should Kartly begin with one app or separate applications for each feature?
Keep the features in one app, with clear internal modules.
Related code runs in one deployment. A developer can follow the product-page flow without crossing several application boundaries. A failed app deployment can still affect every feature inside it.
Deploy each feature as a separate application.
Each application can change separately, but the team must manage more deployments and communication between them. A product-page request may now depend on additional network calls.
Why this choice
Choose one app because the stated problem does not require independent deployments. Inside it, keep product reading, pricing access and page assembly in separate functions or modules.
That is the simplest solution that preserves understandable responsibilities. One app does not mean one large function.
The choice changes if a feature needs a separate release schedule, different resources or stronger failure isolation. Those needs must justify the extra operating work. Simplicity means removing unnecessary complexity, not refusing useful separation.1
For now, we have one app deployment. Next, follow the data through it.
Follow one page to completion
Asha opens a product page. Here is the application-level path in our design:
- The browser asks for a product. The request identifies the product Asha selected.
- The app reads PostgreSQL. It asks for that product’s title, description and .
- The database returns the record. The app now knows which product it is preparing to display.
- The app asks pricing. It sends the product identifier and the information needed to calculate the current price.
- Pricing returns an amount. The app combines that amount with the product details.
- The app returns the page. The browser displays the result.
These steps describe a design choice: this implementation reads the product before asking for its price. We want to establish that the product exists before calculating its price.
The product data moves through the app, but ownership does not move with it. A copy in a response is not a new authoritative record.
Writes follow the ownership rule
When a seller changes a description, our app validates the edit and asks PostgreSQL to save it. It reports success only after the database confirms the save.
A later page request reads that saved description. Pricing remains responsible for the current price, not for product descriptions.
That gives us two paths to inspect: reads retrieve saved facts; writes change the authoritative records.
We are deliberately skipping name lookup, connections and browser rendering. Request lifecycle follows that lower-level journey.
Our successful path is now clear. The next design question is what the app does when a required step cannot finish.
Slow replies change the whole page
Suppose the database returns product details, but pricing does not reply. The app has only part of the answer.
It could wait indefinitely. That avoids choosing an arbitrary stopping point, but it leaves Asha with a spinner.
Waiting also holds resources for longer. Enough unfinished requests can exhaust limited resources such as memory or connections. A slow dependency can therefore disrupt callers that were initially healthy.3
The app needs a : a limit on how long it waits for another component.
A timeout stops the caller waiting. It does not prove that the remote component did no work. This matters especially for requests that change data.3
For our product-page read, the immediate question is narrower: what can we honestly display without a current price?
What should Asha see when pricing exceeds its timeout?
Display an earlier price without explaining that it may be old.
The app can return the page quickly, but the displayed amount might be wrong. A customer could choose an item based on a price Kartly cannot honour.
Show the product without a price and make the limitation explicit.
The app returns the available product details with “Price temporarily unavailable”. It disables buying from that page until it has a current price. Browsing continues, but the sale may be lost.
Fail the entire page.
The app returns an error instead of the product page. This is straightforward, but Asha loses the description and availability information even though those were retrieved successfully.
Why this choice
For this scenario, Kartly’s product team accepts browsing without buying during a pricing failure. Choose the partial page because it preserves useful information without making a false price promise.
If the product requires a confirmed price before showing anything, fail the whole page. An earlier price becomes an option only if the business defines when it is valid and what Kartly promises to honour.
The timeout creates the point where this decision happens. It does not make pricing faster or repair it.
Diagram not drawn yet: kartly-introduction-pricing-timeout.svg
The browser requests a product page from the app. PostgreSQL returns product details. Pricing does not reply before the app’s timeout. The app returns the details with Price temporarily unavailable and buying disabled.
This is already system design: we changed a user-visible failure rule without adding a server.
Detect failure and define recovery
A diagram cannot tell an operator whether the running system is healthy. For this example, record outcomes at each boundary.
| Failure | Customer sees | What we record | Recovery path |
|---|---|---|---|
| App stops | Page cannot load | External page check fails | Operator restores the app |
| Database read fails | Product page unavailable | Database-call error | Restore access; a later request reads again |
| Pricing times out | Product without price | Pricing timeout and partial-page count | A later request tries pricing again |
| Product is missing | Product not found | Normal missing-product result | Seller checks the product listing |
A missing product is not the same event as an unavailable database. The first is a valid answer. The second means the app could not obtain an answer.
Also distinguish recovery from concealment. Returning a partial page contains the effect of a pricing failure; someone still needs to restore pricing.
Do not respond to every failure by immediately retrying many times. Retries add work to the dependency and can worsen overload.2
We have defined a useful failure policy. We have not yet shown that the system can carry festival traffic.
Change the design for a reason
Kartly normally receives about 50 requests a second. Its festival peak reaches 1,000 requests a second.
These are example-world traffic figures, not evidence that the server can handle either workload.
A common belief is that more servers automatically mean a better design. But if every page still waits on the same unavailable pricing service, adding app servers does not remove that dependency.
Before choosing a change, identify the observed limit:
| Evidence | Possible response | What it does not establish |
|---|---|---|
| App CPU is the measured limit | Increase app capacity | That pricing can accept more calls |
| Database calls dominate page time | Investigate those reads | That another app server will help |
| Repeated product reads dominate database work | Consider keeping reusable copies | How to keep those copies current |
| Pricing failures block buying | Improve pricing recovery or the product policy | That product data needs a new database |
These are investigation paths, not diagnoses from one graph. Measurements should connect the customer’s failed request to the component causing it.
Overload can spread when a failing component shifts more work onto others. Adding capacity without understanding that flow can leave the underlying problem unresolved.2
Latency and throughput separates response speed from capacity. Vertical and horizontal scaling develops the options for increasing capacity.
A changed requirement can also justify a change before an incident happens. Requiring browsing to continue during maintenance is evidence, even without a past outage. Functional and non-functional requirements develops those product questions.
The useful explanation is specific: “We changed this part because this requirement or measurement exposed this limitation.”
Common mistakes
- Drawing boxes without following a request. Name the input, work and output at every arrow.
- Treating every failure as the same error. Distinguish missing data from an unavailable component.
- Calling a design simple because it has few boxes. A single app with unclear ownership can still be difficult to change.
- Adding infrastructure without naming its purpose. State the problem it solves and the new failure behavior it introduces.
Where this shows up
- Design: a URL shortener on a single server turns a small feature into a complete read and write design.
- Design: a fast product catalog page revisits repeated reads and the cost of keeping reusable copies current.
- Design: an e-commerce checkout makes failure decisions stricter because orders and money are involved.
We have followed the application’s decisions; next, follow how a browser’s request actually reaches that application.
Check your understanding
Product details load, but pricing never replies. Why might a larger app server leave Asha with the same spinner?Show answer
A seller changes a description. Should the app save it only in its own memory because that is where page assembly happens?Show answer
The product team now forbids showing any product without a confirmed price. What must change in our timeout behavior?Show answer
A developer proposes another database because festival traffic is coming. What should you ask before agreeing?Show answer
Summary
- System design turns a feature and its constraints into components, data flow and explainable decisions.
- Follow one request through every required component; a healthy app can still depend on an unhealthy service.
- Give saved data a clear owner, and distinguish reading a copy from changing the authoritative record.
- Define what customers see when a dependency fails, how you detect it and how service returns.
- Start with the simplest design that meets the requirements. Add complexity when evidence explains why it is needed.