lumo

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

  1. Turn a feature and its constraints into components, data flow and explainable decisions.
  2. Trace a product-page request through a small system.
  3. Explain how a slow dependency affects customers and changes a design.
  4. 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.

Check yourselfAnswer in your head first, then reveal
Kartly’s app server is healthy, but the pricing service never replies. Is the product page guaranteed to work?Show answerHide answer
No. In this design, the app needs a pricing reply before it can finish the page. A healthy component does not make the whole request healthy.

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.

The page succeeds only when the required parts complete their work. The arrows matter as much as the boxes.

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?

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:

  1. The browser asks for a product. The request identifies the product Asha selected.
  2. The app reads PostgreSQL. It asks for that product’s title, description and .
  3. The database returns the record. The app now knows which product it is preparing to display.
  4. The app asks pricing. It sends the product identifier and the information needed to calculate the current price.
  5. Pricing returns an amount. The app combines that amount with the product details.
  6. 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.

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.

The components stay the same. The changed design is the app’s explicit behavior when pricing does not answer.

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

We have followed the application’s decisions; next, follow how a browser’s request actually reaches that application.

Check your understanding

Check yourselfAnswer in your head first, then reveal
Product details load, but pricing never replies. Why might a larger app server leave Asha with the same spinner?Show answerHide answer
The missing pricing reply still blocks completion. More app capacity does not remove the dependency or define when the app should stop waiting.
Check yourselfAnswer in your head first, then reveal
A seller changes a description. Should the app save it only in its own memory because that is where page assembly happens?Show answerHide answer
No. In this design, PostgreSQL owns saved product records. Page assembly uses a copy; it does not change where the authoritative description belongs.
Check yourselfAnswer in your head first, then reveal
The product team now forbids showing any product without a confirmed price. What must change in our timeout behavior?Show answerHide answer
Return a page error instead of the partial product page. The earlier choice depended on permission to let browsing continue without buying.
Check yourselfAnswer in your head first, then reveal
A developer proposes another database because festival traffic is coming. What should you ask before agreeing?Show answerHide answer
Ask which requirement or measured limit it addresses, and how requests would use the new database. Higher expected traffic alone does not identify the limiting component.

Summary

  1. System design turns a feature and its constraints into components, data flow and explainable decisions.
  2. Follow one request through every required component; a healthy app can still depend on an unhealthy service.
  3. Give saved data a clear owner, and distinguish reading a copy from changing the authoritative record.
  4. Define what customers see when a dependency fails, how you detect it and how service returns.
  5. Start with the simplest design that meets the requirements. Add complexity when evidence explains why it is needed.

Report a mistake

Tell us what’s wrong in this lesson and we’ll fix it.