System Design
Read at your own pace. Code execution, quizzes, progress tracking, and the AI tutor are available in the interactive view with JavaScript enabled.
Why System Design
From components to whole systems
Now that we've covered Intermediate Java, Data Structures and Algorithms, Database Design, and Software Architecture, you're ready for the layer interviewers often use to test engineering judgment: system design.
Architecture asked: What are the important boundaries and decisions inside a software product?
System design asks: How should this product behave under real-world load, failure, growth, and organizational constraints?
In this final lesson, we'll connect the whole course:
- Java APIs and ownership become service contracts.
- DSA growth thinking becomes capacity estimation.
- Database design becomes consistency, indexing, and partitioning decisions.
- Architecture boundaries become deployable services, queues, caches, and data flows.
The goal is not to memorize famous designs. The goal is to build a repeatable way to reason.

The Interview Loop
A repeatable system design flow
A strong system design answer is not a perfect architecture dumped from memory. It is a conversation that narrows uncertainty.
Use this loop:
- Clarify the product — Who uses it? What must it do?
- Define constraints — Scale, latency, availability, consistency, cost.
- Sketch core data model — Entities, relationships, ownership.
- Design APIs and flows — Reads, writes, background work.
- Choose storage and compute — Databases, caches, queues, services.
- Handle failure and growth — Retries, idempotency, replication, partitioning.
- Review tradeoffs — What did you optimize for? What did you intentionally defer?
This is the system-level version of what you practiced in Architecture Is Decisions: every major choice should answer a force in the problem.

Estimate Before Designing
Capacity estimation is applied growth analysis
In Data Structures and Algorithms, you practiced asking how work grows. In system design, the same habit becomes capacity estimation.
You rarely need perfect numbers. You need numbers good enough to reveal design pressure.
Ask:
- How many users are active?
- How many writes per second?
- How many reads per second?
- How large is one record or object?
- How much data accumulates per day, month, year?
- Which path is hotter: read, write, search, fanout, analytics?
Here's a tiny Java estimator for a notification system. It does not design the system for us, but it tells us whether a single database and synchronous request path are likely to survive.
```java
public class Main {
public static void main(String[] args) {
long dailyActiveUsers = 2_000_000;
int notificationsPerUserPerDay = 8;
int averageNotificationBytes = 600;
int readFanoutPerNotification = 3;
long writesPerDay = dailyActiveUsers * notificationsPerUserPerDay;
double writesPerSecond = writesPerDay / 86_400.0;
double readsPerSecond = writesPerSecond * readFanoutPerNotification;
double storageGbPerDay = writesPerDay * averageNotificationBytes / 1_000_000_000.0;
double storageTbPerYear = storageGbPerDay * 365 / 1_000.0;
System.out.printf("Writes/day: %,d%n", writesPerDay);
System.out.printf("Average writes/sec: %.1f%n", writesPerSecond);
System.out.printf("Estimated reads/sec: %.1f%n", readsPerSecond);
System.out.printf("Storage/day: %.2f GB%n", storageGbPerDay);
System.out.printf("Storage/year: %.2f TB%n", storageTbPerYear);
if (writesPerSecond > 100) {
System.out.println("Design pressure: consider async processing and write partitioning.");
}
}
}
```
Find The Pressure
A product has modest write volume, but every user opening the home page triggers thousands of personalized lookups across many followed accounts.
Which system design concern should you investigate first?
APIs Shape Systems
The external contract drives the internals
From Intermediate Java, you already know APIs are ownership boundaries. In system design, APIs become the visible edge of a distributed system.
For a simple file upload product, compare two API styles:
Synchronous upload
Client sends file → service stores file → service writes metadata → response returns success.
Good for small files and simple consistency. Risky for huge files, slow networks, retries, and partial failure.
Two-step upload
Client asks for upload URL → client uploads directly to object storage → service records metadata asynchronously.
Better for large files and scaling bandwidth. More moving parts, but each part has a clearer job.
A good interview answer explains why an API shape fits the constraints, not just what endpoints exist.

Design For Failure
Distributed systems fail in partial ways
Inside one Java process, a method call either returns, throws, or hangs. Across services, failure becomes more ambiguous:
- Did the request reach the other service?
- Did it succeed but the response get lost?
- Is the dependency slow, overloaded, or down?
- Will retrying duplicate work?
This is why system design emphasizes:
- Timeouts — never wait forever.
- Retries with backoff — avoid retry storms.
- Idempotency — safely repeat an operation.
- Circuit breakers — stop calling a dependency that is clearly unhealthy.
Here is a small simulation of a circuit breaker protecting a caller from repeatedly hitting a failing dependency.
```java
import java.util.concurrent.atomic.AtomicInteger;
public class Main {
static class UnstablePaymentGateway {
private final AtomicInteger calls = new AtomicInteger();
String charge() {
int call = calls.incrementAndGet();
if (call <= 4) {
throw new RuntimeException("gateway timeout");
}
return "charged";
}
}
static class CircuitBreaker {
private int failures = 0;
private final int failureThreshold;
private boolean open = false;
CircuitBreaker(int failureThreshold) {
this.failureThreshold = failureThreshold;
}
String call(UnstablePaymentGateway gateway) {
if (open) {
return "skipped: circuit open";
}
try {
String result = gateway.charge();
failures = 0;
return result;
} catch (RuntimeException ex) {
failures++;
if (failures >= failureThreshold) {
open = true;
}
return "failed: " + ex.getMessage();
}
}
}
public static void main(String[] args) {
UnstablePaymentGateway gateway = new UnstablePaymentGateway();
CircuitBreaker breaker = new CircuitBreaker(3);
for (int i = 1; i <= 6; i++) {
System.out.println("Attempt " + i + " -> " + breaker.call(gateway));
}
}
}
```
Repeat Safely
A request is designed so that sending it multiple times has the same intended effect as sending it once. This property is called ________.
Tradeoff Vocabulary
Good system design is tradeoff design
A senior design answer sounds less like: Use Kafka, Redis, Kubernetes, and sharding.
It sounds more like: Because writes are bursty and downstream processing can lag by seconds, I would put a durable queue between ingestion and processing. That improves resilience and absorbs spikes, but it adds operational complexity and eventual consistency.
Use a tradeoff vocabulary:
Choice Helps Costs Cache Latency, read load Staleness, invalidation complexity Queue Burst handling, decoupling Delay, duplicate delivery, ordering concerns Read replica Read scale Replication lag Sharding Write/storage scale Cross-shard complexity Denormalization Query speed Update complexity, inconsistency risk Strong consistency Correctness Latency, availability limitationsNotice how this connects to Database Design and Software Architecture: every optimization changes ownership, consistency, and operational burden.

Final Design Checklist
Your reusable system design checklist
When you face an interview prompt or a real architecture proposal, walk through this checklist:
- Product goal — What user problem are we solving?
- Core operations — What are the important reads, writes, and background jobs?
- Scale assumptions — QPS, storage, latency, availability, growth.
- Data ownership — Which service owns which data?
- API shape — What contract do clients depend on?
- Storage choice — Relational, document, key-value, search, object storage, warehouse.
- Hot paths — Where will fanout, contention, or large scans happen?
- Failure plan — Timeouts, retries, idempotency, queues, fallbacks.
- Observability — Metrics, logs, traces, alerts, SLOs.
- Tradeoff review — What did we gain, and what did we make harder?
This course started with better Java habits and ends with system judgment. That is the arc of becoming a stronger engineer: write solid code, choose the right structures, model data carefully, set boundaries deliberately, and design systems that survive reality.
Keep practicing with one constraint at a time. The best system designers are not people who know every tool; they are people who ask better questions under uncertainty.
