πŸ“š Study Notes / Home / HLD / Session 1
Session 01 Β· System Design 101

How big websites stay fast for millions of people

Welcome to your very first High-Level Design class. We assume you've studied none of this before. Every topic starts with a tiny "explain like I'm 5" story, then we slowly go deeper with real examples and real numbers. By the end you'll understand what a system designer actually worries about β€” and why apps like Instagram or Amazon don't just fall over when the whole world shows up at once.

⏱ 39 min readπŸ“– 7 topics

1 What is High-Level Design (HLD) & why it matters


Explain like I'm 5

Imagine you can cook a great dinner for your family of four. Easy! Now imagine you have to cook for a whole stadium of 100,000 hungry people β€” at the same time, fast, and without anyone getting a cold plate. Suddenly you need many kitchens, many cooks, a way to share the work, and a backup plan for when one oven breaks. High-Level Design is the plan for the giant kitchen, not the recipe for one dish.

High-Level Design (HLD) is the practice of planning how a whole software system fits together before you write the detailed code. It describes the big building blocks β€” servers, databases, caches, load balancers, queues β€” and how they talk to each other to serve many users at once, reliably and quickly. It deliberately stays "high level": it cares about boxes and arrows, not about which exact line of code runs inside each box.

HLD vs LLD β€” two different "design" jobs

In our class the very first distinction was between two kinds of design, and people mix them up constantly. Low-Level Design (LLD) is the architecture of the code itself. HLD is the architecture of the servers / machines that run the code.

Low-Level Design (LLD)High-Level Design (HLD)
Architecture of the code.Architecture of the servers / machines that run the code.
How to structure the database schema.How requests and data are spread across many machines.
The entities and their relationships.Load balancers, replicas, caches, queues β€” the big boxes.
The code design patterns.How the system survives crashes, slowness, and scale.
How you lay out the various components inside your code.How the whole system behaves from 100 to 1 billion users.
Why LLD matters too

LLD helps us keep the code maintainable as the code complexity and the team size scale up. It's about humans and code. HLD, in contrast, is about machines and traffic β€” it's the study of "as we go from 100 users to 1 billion users (planet scale / web scale), what challenges arise, and how do we handle them?"

Writing a program vs designing a system

This is the single most important mindset shift in this whole subject, so let's nail it. Writing one program means solving a problem once, for one run, usually on one machine. Designing a system means making that solution work for millions of users, continuously, across many machines, even when parts of it fail.

QuestionWriting a single programDesigning a system (HLD)
Who uses it?One user, or one run on your laptop.Thousands to millions of users at the same time.
What if it crashes?You re-run it.It must keep working β€” other machines take over automatically.
How fast?"Fast enough for me."Fast for everyone, everywhere, under heavy load.
How much data?Fits in memory or one file.Terabytes/petabytes spread across many machines.
Main worryCorrect logic.Scale, reliability, latency, cost β€” and correct logic.
The one big idea

HLD answers two questions about a system: "What are the pieces?" (the what) and "Why are they arranged this way?" (the why). A good design isn't the one with the most boxes β€” it's the one whose every box has a clear reason to exist, chosen to meet the system's specific requirements.

Functional vs non-functional requirements

Before designing anything, you must know what the system has to do. Requirements come in two flavours, and beginners almost always forget the second one.

A functional requirement describes what the system does β€” its features. "Users can post a photo." "Users can search for a product." "A payment can be refunded." If you can phrase it as "the system shall let the user ___", it's functional.

A non-functional requirement (NFR) describes how well the system must do it β€” its qualities under load. These are the heart of HLD. The four you'll meet constantly:

QualityPlain-English meaningTypical way to measure it
LatencyHow long one request takes to get a response β€” the wait you feel.Milliseconds (ms), often reported as p50/p95/p99 (the 99th-percentile slowest request).
ThroughputHow many requests the system can handle per second β€” total capacity.Requests per second (RPS) or queries per second (QPS).
AvailabilityWhat fraction of the time the system is up and answering.A percentage of uptime, e.g. "99.9%" β€” the famous "nines".
ConsistencyWhether everyone sees the same, up-to-date data at the same moment.Strong (always latest) vs eventual (catches up after a short delay).
Latency vs throughput β€” don't mix them up

They sound similar but are different. Latency is about a single trip β€” how long you wait at the checkout. Throughput is about the whole store β€” how many shoppers get served per minute. A system can have low latency but low throughput (one fast checkout) or high throughput but high latency (100 slow checkouts). Great systems want low latency AND high throughput.

The "nines" of availability

Availability is written as nines because each extra nine is dramatically harder. "Three nines" (99.9%) allows about 8.7 hours of downtime per year. "Four nines" (99.99%) allows only ~52 minutes per year. "Five nines" (99.999%) is just ~5 minutes per year. Every nine costs real money and engineering effort β€” this is a classic trade-off we keep meeting.

Watch out: requirements pull against each other

You usually cannot max out every quality at once. Strong consistency can hurt latency. Very high availability often forces you to relax consistency. Lower cost might mean fewer backup machines (lower availability). HLD is largely the art of picking the right trade-offs for this product β€” there is rarely one "correct" answer.

Worked example: designing a photo-sharing app

Suppose your boss says "build something like Instagram." Before any code, you write down:

  • Functional: users can upload a photo, follow others, and see a feed of posts from people they follow.
  • Non-functional: the feed should load in under 200 ms (latency); the system should handle 50,000 requests/second at peak (throughput); it should be up 99.95% of the time (availability); it's OK if a brand-new post takes a second or two to appear in everyone's feed (we choose eventual consistency to keep things fast).

Notice how those numbers immediately suggest what we'll need: more than one server, something to spread the load, caching, and a database that can scale. Every later session in this subject is really about building those pieces β€” and it all starts from requirements like these.

Recap HLD is the big-picture plan for a system that serves many users reliably and quickly β€” boxes and arrows, not lines of code. It differs from "writing a program" because it must survive scale and failure. Always capture functional requirements (what it does) and non-functional ones β€” latency, throughput, availability, consistency β€” and accept that these trade off against each other.

2 How the internet works (end to end)


Explain like I'm 5

You want to send a letter to a friend, but you only know their name, not their home address. So first you ask a giant phone book for the address (that's DNS). Then you write your letter, the post office cuts it into numbered envelopes so nothing gets lost, ships them across the country, and your friend's house puts them back in order and reads them. The internet does exactly this for your "open this website" request β€” millions of times a second.

To design systems that live on the internet, you need a mental model of how a request actually travels. Let's build it from the ground up with four core ideas: the client/server model, DNS, IP, and TCP β€” then watch them work together when you press Enter on a URL.

The client/server model

Almost everything online follows the client/server model. A client is the program that asks for something β€” your browser, a phone app. A server is a program (running on some machine in a data centre) that listens for requests and responds. The client says "please give me this page"; the server says "here it is." Your laptop is a client; YouTube's machines are servers.

DNS β€” the internet's phone book

Humans remember names like google.com; computers route traffic using numbers called IP addresses (e.g. 142.250.72.78). The DNS (Domain Name System) is the giant, distributed lookup service that translates a human-friendly name into the machine's IP address. This translation is called DNS resolution.

Who gives a website its IP? Registrars & ICANN

There's a step before DNS can even answer. Say Joshua wants to launch a website. First he must purchase a domain. He does that through a domain registrar β€” an intermediary that facilitates the purchase and management of domain names. Examples: GoDaddy, Namecheap, Cloudflare. The registrar then relays to ICANN (the central naming authority) that Joshua has bought this domain, and ICANN stores the IP address of Joshua's machine against the domain name he purchased.

You also need an Internet Service Provider (ISP) to be on the internet in the first place β€” the company that provides you the connection. In India that's Airtel / Jio / Vi; elsewhere ACT / Hayai / Starlink / Comcast. And an IP address is the (kind-of) unique address assigned to each machine connected to a computer network.

Why ICANN alone can't answer every lookup

There are roughly 5 billion internet users and somewhere between 100 billion and 1 trillion devices online. If every "what's the IP for this name?" question had to hit ICANN's servers directly, ICANN would become a bottleneck and a single point of failure β€” "I Can β‡’ I Can't!" The fix is the same shape we'll see again and again in this course: distribute the work.

DNS β€” who actually maintains it?

So DNS is the distributed answer to that bottleneck. DNS maintains a list of IP addresses for each domain name (a name can map to many IPs). ICANN remains the central authority, but the actual DNS servers are maintained by lots of different entities β€” and in fact anyone can run a DNS server:

  • Software companies β€” Google, Amazon, Meta
  • Governments, militaries, research organizations, educational institutes
  • ISPs β€” Airtel, BSNL

You can connect to any DNS server you like (e.g. Google Public DNS). And if you never configure one yourself? Your ISP also runs a DNS server, and that one is already configured in your router by default β€” so lookups still work.

IP β€” the addressing & delivery layer

The IP (Internet Protocol) is the set of rules for addressing and routing. Every machine on the internet has an IP address, like a postal address. IP's job is to get a packet of data from one address to another, hopping through many routers along the way. IP by itself is "best effort" β€” it doesn't guarantee packets arrive, or arrive in order. That guarantee is TCP's job.

TCP β€” the reliable courier

TCP (Transmission Control Protocol) sits on top of IP and adds reliability. It chops your data into numbered packets, makes sure each one arrives (re-sending any that get lost), and puts them back in the correct order at the other end. Before sending real data, the two sides do a quick three-way handshake (SYN β†’ SYN-ACK β†’ ACK) to agree they're both ready β€” like saying "Hello? / Yes, I hear you / Great, let's talk."

Where does HTTP fit?

HTTP (HyperText Transfer Protocol) is the language the browser and web server speak over a TCP connection. TCP reliably carries the bytes; HTTP defines what those bytes mean β€” "GET me this page," "here's the page, status 200 OK." When you see https://, the S means the connection is additionally encrypted with TLS so nobody in between can read it.

What happens when you type a URL and hit Enter

This is the classic interview question, and it ties everything above together. Here's the whole request β†’ response lifecycle:

⌨️
1. URL typed
You enter google.com, press Enter
β†’
πŸ“–
2. DNS lookup
Name β†’ IP address
β†’
🀝
3. TCP handshake
Open a connection to that IP
β†’
πŸ“¨
4. HTTP request
GET the page (over TLS if https)
β†’
πŸ–₯️
5. Server works
Builds the response
β†’
πŸ“¦
6. HTTP response
Sends HTML/JSON back
β†’
🎨
7. Browser renders
You see the page

Let's narrate it once in words. You type google.com and hit Enter (1). Your computer asks DNS for the IP address behind that name (2). With the IP in hand, your machine opens a TCP connection to that server using the three-way handshake (3). Over that connection it sends an HTTP request β€” essentially the text "GET / please" (4). The server runs its code, perhaps querying a database, to build the answer (5). It sends back an HTTP response: a status code plus the page's HTML or data (6). Finally your browser reads that HTML, fetches any extra bits (images, CSS, scripts) the same way, and paints the page on screen (7).

What a raw HTTP request & response look like

The browser sends something close to this plain text over the TCP connection:

GET /search?q=cats HTTP/1.1
Host: www.google.com
User-Agent: Mozilla/5.0
Accept: text/html

And the server replies with a status line, headers, then the body:

HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 1256

<!DOCTYPE html><html>…the page…</html>

That 200 is an HTTP status code meaning "success". You'll also meet 404 (not found), 500 (server error), and 301 (moved). Methods besides GET include POST (send data), PUT, and DELETE.

Key takeaway

Every web interaction is the same little dance: resolve the name (DNS) β†’ connect reliably (TCP over IP) β†’ ask in HTTP β†’ get a response β†’ render. Once you internalise this, every system we design later is just adding smarter machinery behind step 5, where "the server works."

Recap The web runs on the client/server model. DNS turns names into IP addresses; IP routes packets between machines; TCP makes that delivery reliable and ordered; HTTP is the request/response language spoken on top. Typing a URL kicks off: DNS lookup β†’ TCP handshake β†’ HTTP request β†’ server work β†’ HTTP response β†’ render.

3 Case study: del.icio.us & the idea of an MVP


Explain like I'm 5

Imagine you keep a little notebook of your favourite shop addresses. The problem: the notebook lives in one drawer at home, so when you're at school you can't see it. What if you could write your list on a magic board that you can read from any computer, anywhere? That's all del.icio.us was β€” a place to save your bookmarks so you can reach them from any machine. We'll use this tiny app as our running example for the whole course.

We anchor everything in a real product. del.icio.us was a simple bookmarking service, launched in 2003 by a creator named Joshua. For context on how early that is: YouTube came in 2004, Amazon AWS in 2006, Google Chrome in 2008.

The motivation

Back then, most people did not have the internet at home, and browser bookmarks were stored locally β€” the bookmarks you saved on one computer were not available on any other computer. So the pitch was: "Let's create a bookmarking service where people store their personal bookmarks on my website. My users can then access their bookmarks from any system, as long as they have an internet connection."

What's the smallest version worth building? The MVP

You don't build every feature on day one. You build a Minimum Viable Product (MVP) β€” also called a proof of concept or prototype. The three words each carry weight:

WordWhat it means
ProductIt solves a problem.
ViableIt's usable β€” it actually demonstrates the solution.
MinimalThe minimal set of features needed to be viable β€” nothing more.
The del.icio.us MVP feature list

For the bookmarking service, the minimal-but-viable feature set is:

  • A user should be able to identify themselves β€” registration + login. (Note: logout can be added later.)
  • A user should be able to add bookmarks.
  • A user should be able to view their bookmarks.

Things like deletion, updating, sharing, and so on are deliberately left out β€” they're not needed for the MVP and can be implemented later.

Personal computer vs server

Joshua initially ran the website from his personal laptop. Two problems showed up immediately:

  • When Joshua went to sleep and turned his laptop off β€” the website went down.
  • When Joshua downloaded a movie or played a game β€” the website became extremely slow.

The lesson: a personal computer is for personal use. To run a service you need a server β€” a dedicated computer whose sole purpose is to provide a (web) service over the internet. Joshua had to stop using that machine for personal use.

A 2003-era machine, for scale

To feel how weak hardware was back then, here's a first PC from 2007 (computers in 2003 were even worse): cost β‚Ή35,000, 128 MB RAM, 40 GB HDD, an Intel Pentium Dual Core (2 cores, 2.3 GHz), and an 8 Kbps dial-up BSNL connection. Keep those numbers in mind β€” they make the scaling math in the next topic real.

Designing the database

A first cut of the schema for the bookmarking service:

users ( id, name, password )

user_bookmarks ( user_id, URL )
  user_id : bigint        (8 bytes)
  URL     : varchar(1000) (1000 bytes)
Why id can't be a 4-byte integer

A 4-byte integer only has about 4 billion possible values:

4-byte integer
  unsigned : 0 … 2^32        (0 … 4 billion)
  signed   : -2^31 … 2^31-1  (-2 billion … 2 billion)

There are 8+ billion people in the world β€” 4 billion possible IDs is not enough to give everyone a unique id. That's exactly why the schema uses a bigint (8 bytes) for the user id.

Recap del.icio.us (2003, by Joshua) let people store bookmarks online and reach them from any machine. You start with an MVP β€” Product (solves a problem), Viable (usable), Minimal (smallest feature set): here just register/login, add bookmarks, view bookmarks. A real service needs a dedicated server, not a personal laptop. And even the schema teaches scale β€” user ids need a bigint because 4-byte integers top out around 4 billion.

4 Challenges at scale β€” what breaks at 100 million users


Explain like I'm 5

A small lemonade stand with one kid serving works great for the 5 neighbours who show up. But if the entire town lines up at once, that one kid can't pour fast enough, runs out of cups, the table collapses under the crowd, and if the kid gets tired and sits down, everyone goes thirsty. The same things that worked for a few customers break badly when a crowd arrives. Computers have the exact same problem.

When your app is new, one server handling everyone is perfectly fine. The trouble starts as you grow from 100 users to 100 million. Suddenly that single machine β€” let's call it the monolith on one box β€” runs into hard physical limits. Let's see exactly what breaks, with realistic numbers.

The one big idea of this whole course

Simple problems become challenging at large scale. High-Level Design is the study of the problems that arise at scale, and the solutions for those problems β€” as we go from 100 users to 1 billion users (planet scale / web scale). "Scale" itself has two axes: the amount of data and the number of requests per second.

The interview question that started it all

This was posed as a real Staff Engineer @ Google interview question, and it's the perfect illustration of "easy turns hard at scale."

Interview question: sort the strings in a file

Given a file containing strings, sort the strings in dictionary (lexicographic) order.

input:  cat, dog, apple, laptop, class, high, level, design
output: apple, cat, class, design, dog, high, laptop, level

Very, very easy! In Python it's basically three lines:

with open('data.txt', 'r') as f:
    data = f.readlines()

print(sorted(data))

The catch: there is 50 Petabytes of data. Suddenly the three-line answer is useless β€” it won't even fit in a single machine.

A quick units refresher (so the numbers mean something)

To reason about scale you must be fluent in data sizes. Note the difference between the decimal units (Kilo = 1,000) and the binary ones (Kibi = 210 = 1,024).

UnitSize
1 bitThe fundamental unit of information (a 0 or a 1).
1 nibble4 bits
1 byte8 bits
1 Kilobyte (KB)1,000 bytes  (1 Kibibyte / KiB = 1,024 bytes)
1 Megabyte (MB)1 million bytes
1 Gigabyte (GB)1 billion bytes
1 Terabyte (TB)1 trillion bytes
1 Petabyte (PB)1 quadrillion bytes
So how big is 50 Petabytes?

50 PB = 50,000,000 GB (fifty million gigabytes!). Will that fit in your RAM? No. Will it fit on your HDD? No. The data must be stored in a distributed manner across many machines. For reference, Google has more than 10 million servers across the globe.

The shape of the solution β€” and everything that can go wrong

At a high level the job is simple to state: Collect data β‡’ Sort β‡’ Store the data back. The hard part is that, spread across millions of machines, an enormous number of things can go wrong:

CategoryWhat can go wrong
Network issuesThe network is slow, or goes down.
Server issuesA server is slow, goes down, or is malicious.
Heterogeneity issuesDifferent hardware models; different operating systems installed.
Human errorThe software is buggy.
Data errorA hard disk is corrupt.

Despite all of these challenges, you must still complete the task β€” both efficiently and correctly. That tension is the heart of HLD.

The four resources that run out

A single server is just a computer. It has four finite resources, and at scale you slam into each one:

ResourceWhat it doesWhat "running out" looks like
CPUDoes the computation β€” runs your code.Requests queue up waiting for a free core; responses get slow, then time out.
Memory (RAM)Holds data the program is actively using.Server runs out of RAM, starts swapping to disk (1000Γ— slower) or crashes (out-of-memory).
Disk (storage & I/O)Stores data permanently; reads/writes it.Disk fills up, or read/write speed becomes the bottleneck β€” the database can't keep up.
Network (bandwidth)Moves data in and out over the wire.The network card saturates; data can't get out fast enough no matter how fast the CPU is.
Putting real numbers on it

Say one decent server can comfortably handle about 1,000 requests per second before it slows down. Watch what happens as you grow:

  • 100 users, a few requests each β†’ maybe 10 RPS. The server yawns. 😴
  • 100,000 users β†’ easily a few thousand RPS at peak. You're already past what one box can do β€” pages start lagging.
  • 100,000,000 users β†’ potentially millions of RPS at peak. One server isn't 1% of the way there. It would melt instantly.

You'd need thousands of servers working together. That coordination problem is exactly what the rest of HLD is about.

Back to del.icio.us: when does the disk fill up?

Let's make this concrete with our case-study app. del.icio.us went viral β€” Joshua was suddenly getting millions of new bookmarks each day, on a 2003-era machine with only a 40 GB hard disk. Let's do the napkin math.

Worked example: del.icio.us disk-space math

Q: How large is each row in the user_bookmarks table? The user_id is 8 bytes and the URL is up to 1000 bytes, so a row is approximately 1 KB. (URLs really are that long β€” think of a giant Amazon product link full of tracking parameters.)

Q: How much data do we add per day at 1 million new bookmarks/day?

1 KB / bookmark  Γ—  1,000,000 bookmarks / day
= 10^3 bytes  Γ—  10^6 / day
= 10^9 bytes / day
= 1 GB / day

Q: In how many days do we run out of disk space?

40 GB  /  (1 GB / day)  =  40 days

In just 40 days, Joshua's server runs out of disk space. 😱

Disk is not the only thing that runs out

Is disk space the only resource that needs scaling? No β€” the website is also receiving a ton of requests. CPU is needed to handle network requests and process DB queries; Disk to store the data; RAM to hold the request contexts; and Network to carry the requests. Every resource needs to be scaled.

Latency creeps in too

Scale isn't only about volume β€” it's about distance and delay. A user in Australia hitting a server in Virginia pays a physical penalty: data can't travel faster than light. A round trip across the world is easily 150–300 ms just in network travel, before any work is done. Pile on a busy CPU and a slow database query, and your "fast" app feels sluggish to half the planet. As load rises, latency doesn't grow gently β€” it tends to spike sharply once a resource nears 100%.

The scariest problem: the single point of failure

The worst issue with one server isn't speed β€” it's fragility. A single point of failure (SPOF) is any one component whose death takes down the entire system. If everything runs on one machine and that machine reboots, loses power, or its disk dies, 100% of your users are offline. No amount of "the code is correct" saves you. Your availability (Topic 1) drops to zero in an instant.

Vertical scaling buys time, not a cure

Your first instinct β€” "just buy a bigger server!" β€” does help for a while (we'll formalise this as vertical scaling in the next topic). But it has a hard ceiling: the biggest machine money can buy is still one machine, so it's still a single point of failure, and it gets exponentially more expensive. Real scale needs a different shape.

Recap As users grow from 100 to 100 million, a single server hits hard limits on CPU, memory, disk, and network; latency rises (and spikes near capacity), partly from sheer distance; and worst of all, one box is a single point of failure whose death means total outage. The fix isn't a faster box β€” it's a smarter shape, which is the next topic.

5 Vertical vs Horizontal scaling


Explain like I'm 5

Your one delivery van is too small for all the parcels. You have two choices. Option A: buy one GIANT truck that holds more (that's scaling up). Option B: buy ten normal vans and split the parcels between them (that's scaling out). The giant truck is simple but there's a biggest-truck-that-exists limit β€” and if it breaks down, nothing gets delivered. Ten vans is more to manage, but you can keep adding vans forever, and if one breaks, the other nine still deliver.

There are exactly two ways to give a system more power, and knowing the difference is core HLD vocabulary.

Vertical scaling (scaling up)

Vertical scaling means making a single machine more powerful β€” adding more CPU cores, more RAM, faster disks. You keep one server; you just beef it up. It's wonderfully simple because nothing about your software has to change.

Horizontal scaling (scaling out)

Horizontal scaling means adding more machines and spreading the work across all of them. Instead of one super-server, you run many ordinary servers side by side. This is how every massive system on earth is built β€” but it introduces a new question: how do you split the work, and how do the machines stay in sync?

AspectVertical (scale up)Horizontal (scale out)
HowBigger single machine (more CPU/RAM/disk).More machines working together.
Simplicityβœ… Very simple β€” no code changes.⚠️ More complex β€” needs coordination.
Limit / ceiling❌ Hard ceiling β€” biggest box that exists.βœ… Practically unlimited β€” keep adding nodes.
Cost curve❌ Gets exponentially pricey at the top end.βœ… Uses cheap commodity machines; near-linear.
Single point of failure?❌ Yes β€” it's still one box.βœ… No β€” others survive if one dies (redundancy).
Downtime to scale❌ Often must reboot to upgrade hardware.βœ… Add/remove machines live, no downtime.
Worked example: buy one giant server, or many cheap laptops?

Instead of one expensive super-server, Joshua could just buy a pile of cheap laptops. At β‚Ή35,000 each, β‚Ή1 crore buys:

1,00,00,000 β‚Ή  /  35,000 β‚Ή  =  285 laptops

And actually he'd get more β€” because of economy of scale, buying directly from the manufacturer in bulk is cheaper per unit. Maybe he gets 500 laptops for the same money. That's the spirit of horizontal scaling: lots of cheap, ordinary machines instead of one premium one.

Which one should you use?

  • Vertical scaling is easy β€” all you have to do is throw money at it. But it has limits: you cannot scale infinitely. It's bounded by current technology, and it's more costly.
  • Horizontal scaling is not limited by current technology β€” you can scale (pretty much) infinitely. Eventually you have to scale horizontally, because vertical scaling will run out.
  • But horizontal scaling is a huge pain β€” it is extremely difficult. (The entirety of the HLD curriculum is about the challenges of scaling horizontally and how to solve them.)
In reality, you use both

You vertically scale while it's financially feasible and simple. When it becomes too costly, or you run out of hardware capability, you scale horizontally. Some workloads mandate both:

  • del.icio.us: horizontal is all you need β€” every machine can be cheap and not very powerful.
  • Video processing: you must scale horizontally, and each server needs a powerful GPU, CPU, and lots of RAM β€” so you're also scaling vertically.
  • Large Language Models (ChatGPT): a ~7 TB neural-network model needs roughly 7 TB of RAM + 7 TB of GPU memory + 7 TB of disk per copy β€” and with ~500 million users you also need horizontal scaling. Both, heavily.

How far can vertical scaling actually go? (Jan 2025)

To make the "hard ceiling" concrete, compare a typical server to the biggest one money could buy as of January 2025:

ResourceTypical serverMax server (Jan 2025)
CPU1 – 16 cores370 cores (Γ—2 on a single motherboard)
RAM1 GB – 32 GB12 TB
HDD500 GB – 8 TB2 PB
Network10 Mbps – 1 Gbps10 TBps

Impressive β€” but still one machine, and therefore still a single point of failure with a real upper bound. That's why planet-scale systems must eventually go horizontal.

It's not strictly either/or

In practice you often do both: pick a reasonably powerful machine (vertical), then run many of them (horizontal). But for systems that must reach enormous scale and stay always-on, horizontal is the answer β€” it's the only path that removes the single point of failure and has no hard ceiling.

The secret ingredient: statelessness

Horizontal scaling only works smoothly if any server can handle any request. That requires the servers to be stateless: an individual server stores no user-specific memory between requests. Each request carries (or can look up) everything needed to be served by any machine.

Why statelessness is the enabler

If a server remembered "user Alice is logged in and her cart has 3 items" only in its own local memory (stateful), then Alice's next request must land on that exact same server β€” or her cart vanishes. That ruins the freedom to spread work around, and if that one server dies, Alice's session dies with it. By keeping servers stateless β€” pushing shared data into a database or cache that all servers reach β€” any request can go to any server. That is what makes "just add more machines" actually work.

Worked example: the shopping cart
  • Stateful (bad for scaling): Server A holds Alice's cart in its own RAM. Add a Server B and Alice's next click lands there β€” empty cart, very confused customer.
  • Stateless (scales beautifully): The cart lives in a shared database. Every request includes Alice's session token; any server reads her cart from the database, serves her, and forgets her the instant the response is sent. Now you can run 5 servers or 5,000, and add or remove them at will.

This is also why your login still works after a site quietly swaps which machine serves you β€” the "you" lives in shared storage, not in any one server's head.

Recap Vertical scaling = a bigger single box: simple, but capped, costly at the top, and still a single point of failure. Horizontal scaling = more boxes: complex, but unlimited, cheaper per unit, and resilient. Horizontal scaling depends on statelessness β€” keep per-user state in shared storage so any server can serve any request.

6 Introduction to load balancing


Explain like I'm 5

Picture a busy supermarket with ten checkout lanes. If everyone piled into lane 1, that cashier would drown while nine lanes sat empty. So there's a friendly person at the front saying "you go to lane 3, you go to lane 7" β€” keeping every lane evenly busy. A load balancer is that friendly traffic director, but for web servers.

In the last topic we said "just add more servers." But that immediately raises a question: when a user's request arrives, which of your many servers should handle it? The user only typed one address β€” they don't know or care that ten machines exist behind it. Something has to stand in front and decide. That something is a load balancer.

What a load balancer is

A load balancer (LB) is a component that sits in front of your pool of servers and distributes incoming requests across them. To the outside world, the load balancer is the website β€” users connect to it, and it quietly forwards each request to one of the servers behind it (often called the backend servers, or the server pool).

πŸ‘₯
Users
Many requests arrive
β†’
βš–οΈ
Load balancer
Picks a healthy server
β†’
πŸ–₯️
Server pool
App servers 1…N share the work
β†’
πŸ—„οΈ
Shared data
Database / cache for all servers

Why horizontal scaling needs a load balancer

Without a load balancer, having many servers is almost useless β€” you'd have no automatic way to route users across them. The load balancer is the piece that makes the pool act like one big, reliable service. It plays three high-level roles:

RoleWhat it meansWhy it matters
Distribute trafficSpread incoming requests evenly across all servers.No single server gets overwhelmed while others idle β€” you use all your capacity.
Health checksConstantly ping each server ("are you alive?") and stop sending traffic to any that fail.A broken server is quietly taken out of rotation; users never notice.
Remove the single point of failureIf one server dies, the LB routes around it to the survivors.This is what finally delivers the high availability from Topic 1.
The question that forces a load balancer to exist

Once you have many servers, the very first puzzle is: which server's IP should the DNS register? You can't list them all and hope for the best. The clean answer is to register one address β€” the load balancer's β€” and let it provide a unified view of the whole backend to the end user (who doesn't care which exact server handles their request) while it distributes the load β€” requests and data β€” evenly across the app/db servers.

How does the LB know which servers are alive?

Servers can crash at any time, and the LB must never forward a request to a dead one. There are two complementary mechanisms, and the difference is a classic interview point β€” who initiates the ping:

MechanismWho pings whomHow a "dead" server is detected
HeartbeatEach server periodically pings the LB: "I'm alive!"If the LB misses heartbeats for a few consecutive intervals, it assumes the server is dead.
Health checkThe LB periodically pings each server: "Are you alive?"If the server fails to respond within the request timeout, the LB assumes it's dead.

But how does the LB even know which servers exist in the first place? Two common approaches:

  • Self-registration: when a server first comes online in the network, it pings the LB to register itself.
  • Configured IP range: you configure a range of IPs in the LB, e.g. 10.11.0.1 … 10.11.2.20, and it watches all of them.

Isn't the load balancer itself a bottleneck?

To answer this, compare what an app server does versus what a load balancer does. An app server (e.g. a Django server handling bookmarking requests) does a lot of work for every request β€” the request travels up through the OSI layers (the web server lives at the Application Layer):

  1. Deserialize the request to get the payload
  2. Authorization β€” check the user's permissions
  3. Fetch the resource from the DB
  4. Process the data
  5. Generate a response
  6. Serialize the response
  7. Send the response

All that work means a typical app server handles only 100 – 1,000 requests/sec. The load balancer, by contrast, does not handle the request β€” it just (1) looks at the IP address in the request, (2) decides which server to forward to, and (3) re-routes it. Because it does so little, a typical LB easily handles 100,000+ requests/sec.

So the LB rarely becomes the bottleneck

One LB can absorb the traffic of ~100–1000 app servers, so it doesn't become a bottleneck easily. But at the scale of Google, a single LB is still not enough β€” which leads to the next worry.

What if the load balancer crashes? (The SPOF question)

If everything funnels through one LB, then yes β€” absolutely β€” the LB is a single point of failure. If it goes down, the entire website goes down. How do we fix it?

  • Tempting but wrong: "put another LB in front of the LBs." That doesn't help β€” you've just added a layer, and the SPOF problem now lives at the first LB instead.
  • The real solution: run multiple LBs, and let the DNS act as the load balancer in front of the load balancers (DNS can hand out different LB IPs to different users).

Which server does the LB pick? Routing algorithms

When a request arrives, a routing algorithm decides which backend server gets it. Two you'll meet right away:

  • Round Robin β€” hand requests to servers in turn: 1, 2, 3, 1, 2, 3, …
  • Consistent Hashing β€” hash a key (e.g. the user) to consistently pick a server, which keeps things stable as servers are added or removed.

The deep dive on these is exactly the topic of Session 2.

Worked example: a server dies at 2 a.m.

You run 4 app servers behind a load balancer. At 2 a.m., Server 3's disk fails. Here's the sequence:

  • The LB's health check to Server 3 stops getting a reply.
  • After a couple of failed checks, the LB marks Server 3 unhealthy and removes it from rotation.
  • New requests are now spread across Servers 1, 2, and 4 only. Each takes a bit more load, but users see no error.
  • In the morning you replace Server 3; once its health checks pass, the LB automatically adds it back.

Compare that to the single-server world of Topic 3, where the same disk failure meant a total, customer-facing outage. That is the power of a load balancer plus statelessness.

The load balancer isn't a new single point of failure (in practice)

Smart question: "if everyone connects to the load balancer, isn't it now the single point of failure?" In real systems you run the LB itself redundantly (two or more, with automatic failover), and cloud providers offer managed, highly-available load balancers. So the SPOF is genuinely removed, not just relocated.

Key takeaway

A load balancer is the front door to a horizontally-scaled system. It turns a messy pile of identical servers into one dependable service by distributing traffic, health-checking the backends, and routing around failures. Horizontal scaling and load balancing are two halves of the same idea.

Coming up in Session 2

We've kept this at the "what and why" level on purpose. How exactly does the LB decide which server gets the next request β€” round-robin? least-connections? Hash the user's IP? And how do we spread data (not just requests) across many machines fairly? Those are the load-balancing algorithms and consistent hashing, and they're the whole of Session 2: Load Balancing & Consistent Hashing.

Recap A load balancer sits in front of your server pool and is what makes horizontal scaling usable: it distributes traffic, runs health checks to skip dead servers, and removes the single point of failure by routing around failures β€” delivering real availability. The specific algorithms it uses are Session 2.

7 How to succeed in HLD


Explain like I'm 5

Learning HLD is like learning to be a great cook by being curious in every kitchen you walk into: "why is the fridge here and the stove there?" You poke at how things work, you chat with other cooks, and you practise a little before the next lesson. Do that and you'll get good fast.

HLD isn't memorised, it's developed as a way of thinking. Here's how to get the most out of this module:

  • Be curious. Ask questions during the doubt session, devour all the reference material we provide, and explore interesting topics on your own.
  • Think about the internals. For any app or software you use, think about its internal design: how might it handle scale, what unique challenges does it face, and how does that affect the features it supports (and doesn't support)?
  • Think of the business use-case of every feature you consider. For example, how does showing a "blue checkmark for read" actually help WhatsApp?
  • Interact with your peers. HLD is best learned through discussion β€” bouncing ideas in a group, talking about what you've seen at work and why, and sharing the tech challenges you and your company face.
  • Solve the assignments before the next class. They're MCQs, so they're quick, but they require critical thinking β€” which is exactly what this module is built to develop.
Recap Succeed in HLD by being curious, reasoning about the internals and business use-case of everything you use, discussing ideas with peers, and doing the assignments before each class.

β˜… Putting it all together


You just built the entire mental foundation of system design. Here's the one-paragraph story that connects all five topics:

High-Level Design is the big-picture plan for serving many users reliably and fast, driven by functional and non-functional requirements β€” latency, throughput, availability, consistency β€” that trade off against each other. Those users reach you over the internet via DNS β†’ TCP/IP β†’ HTTP. A single server can't keep up at real scale: it runs out of CPU, memory, disk, network, suffers rising latency, and is a single point of failure. The cure isn't a bigger box (vertical scaling has a ceiling and stays a SPOF) but more boxes (horizontal scaling), which works only when servers are stateless. And to make a pile of servers behave like one dependable service, you put a load balancer in front to distribute traffic, health-check the pool, and route around failures.

Quick self-check

What's the difference between a functional and a non-functional requirement?

A functional requirement is what the system does (a feature, e.g. "users can post a photo"). A non-functional requirement is how well it does it under load β€” latency, throughput, availability, consistency.

Latency and throughput β€” which is "how long one request takes" and which is "how many per second"?

Latency = how long a single request takes (the wait you feel, in ms). Throughput = how many requests the system handles per second (capacity, in RPS/QPS). Great systems want low latency and high throughput.

You type google.com and hit Enter. What's the very first thing that has to happen?

A DNS lookup β€” translating the human-friendly name "google.com" into the server's IP address. Only then can your machine open a TCP connection and send the HTTP request.

Why is a single server a "single point of failure," and why does that matter more than speed?

If everything runs on one machine and it dies (power, disk, reboot), 100% of users go offline β€” availability drops to zero. No matter how correct or fast your code is, one box means one thing can kill the whole system.

Why does horizontal scaling depend on servers being stateless?

So any request can be handled by any server. If a server kept per-user state in its own memory, that user's later requests would have to return to the exact same server β€” and if it died, the session died with it. Keeping state in shared storage lets you freely add, remove, and swap servers.

Name the three high-level jobs of a load balancer.

(1) Distribute incoming traffic across the server pool, (2) run health checks and stop sending traffic to dead servers, and (3) remove the single point of failure by routing around failures β€” together delivering high availability.

πŸ“š References & Further Reading


Class material

Papers, docs & deep dives

DNS deep dives (from class)

Recommended by the instructor

Resources to avoid

The instructor recommends steering clear of: Medium articles by random authors (prefer companies' engineering blogs over individuals'), GeeksforGeeks, and random YouTube videos by "bhaiyas & didis".