How servers, databases, and networks fit together so a system stays fast and reliable as it grows.
System design is the practice of deciding how servers, databases, caches, and networks fit together so software keeps working as it grows. Instead of trying to optimize specific parts of the code, you are now zooming out and looking at the big picture. You have to actively make decisions about your system and understand the tradeoffs between each choice you make.
I recommend you study the background section in order, but after that you should feel free to jump around and explore topics that interest you. The content in the pages below is carefully designed to help you understand the big picture and tradeoffs between choices. System design is largely about understanding the problem and choosing the best option based on your circumstance.
Every system design conversation should start with these three questions: what is the computer actually doing, how is the application structured, and what does this system need to deliver? This section covers how computers and applications are put together before we even discuss scaling.
What CPU, RAM, and disk each do, and why the speed gaps matter.
The anatomy of a deployed app, from client to server to storage.
Pinning down what a system must do before deciding how.
Quick estimates that tell you if a design is even plausible.
Everything in a distributed system travels over a network, so you need to know how data actually moves. This section covers how machines find each other through DNS, and what the transport layer guarantees you with TCP or gives up for speed with UDP.
IP addresses, ports, and how machines find each other.
Reliable delivery or raw speed: the transport layer's tradeoff.
The phonebook that turns domain names into IP addresses.
An API is the contract between client and server about what you can ask for and what you get back. This section covers protocols and connections that let a server push without being asked, and the design choices that keep legacy clients working as the API grows.
The request-response protocol behind nearly everything on the web.
A two-way connection for when the server needs to push.
REST, GraphQL, and gRPC, and when each one earns its keep.
Interfaces that survive growth without breaking their callers.
The fastest request is the one you never have to make! Caches keep recently used data in fast storage so we don't have to constantly spam our database. CDNs apply the same idea by "parking" copies of files near your users. However, you can't store the same copy forever, meaning you have to figure out when data has gone stale.
Answers repeated reads from fast memory instead of the database.
Cache-aside, write-through, write-back, and when copies go stale.
Serves static files from servers near the user.
Between clients and your servers, you have a layer of middlemen. Load balancers spread traffic so your servers are handling work evenly, gateways handle auth and routing, rate limiters keep one annoying client from ruining things for everyone, and consistent hashing decides which server owns what without reshuffling on every change.
Middlemen that route, shield, and spread traffic across servers.
One front door handling auth, routing, and TLS for everything behind it.
Moves almost nothing when servers join or leave the ring.
Caps request rates so one noisy client can't sink the system.
Most scaling problems end up being database problems. This section starts with the infamous SQL or NoSQL question. From there it covers how data survives the growth of an application.
Tables, joins, and transactions with hard guarantees.
Flexible schemas built to spread across machines.
The data structures that turn full scans into instant lookups.
Copies data across machines for speed and survival.
Splits data across machines when one is no longer enough.
When the network splits, you keep consistency or availability, not both.
Cheap, bottomless storage for files, backups, and blobs.
Some tasks are too big or too slow for a basic request-response loop, which is why message queues and pub/sub systems move that work off to the side to run on its own schedule, while MapReduce and stream processing work their way through datasets that a single machine can't realistically handle.
Lets services hand off work without waiting for each other.
Broadcasts events to every subscriber, with a log you can replay.
Splits a huge job across many machines, then merges the results.
Crunch data in scheduled chunks or process it as it arrives.
The last section zooms out to the shape of the whole system. Should you build one deployable app or many small services? Should services call each other directly or react to events? And how should a cluster of machines agree on a single truth while some of them are failing?
One deployable app or many small services, each at a price.
Services react to events instead of calling each other directly.
How a cluster of unreliable machines agrees on one truth.