System Design Process and Methodologies
Learn about the steps and approaches used to design scalable and efficient systems in simple terms.
This tutorial walks through the complete system design process, from gathering requirements and estimating capacity to high-level design, component, data and API design, security, reliability and testing. Every step uses one running example, a URL shortener, with formatted Java and Spring Boot code. It then compares the most common design methodologies, such as Waterfall, Agile, object-oriented design, microservices and event-driven design, and explains how to choose between them.
1. What Is System Design?
System design is the process of planning and creating a structure for a software system. It involves understanding user requirements, defining system components, and designing how these components work together to meet both functional and non-functional goals.
Think of it like planning a house before building it. An architect decides how many rooms are needed, where the doors and pipes go and how strong the foundation must be, long before anyone lays a brick. System design does the same for software: it decides the parts of the system, how they talk to each other and how they will cope as the number of users grows.
System design works at two levels. High-level design shows the big building blocks, such as services, databases and caches. Low-level design describes each block in detail, such as classes, methods and database tables. See High-Level vs Low-Level Design for a closer look.
2. Why Is System Design Important?
| Benefit | What it means |
|---|---|
| Scalability | Helps the system handle growth in users and data efficiently |
| Performance | Ensures the system responds quickly and works smoothly under load |
| Reliability | Reduces the chance of system failures or downtime |
| Maintainability | Makes future updates and changes easier to implement |
| Security | Protects data and users by planning safeguards from the start |
| Lower cost | Fixing a design problem on paper is far cheaper than rewriting a running system |
A system built without a design often works for the first hundred users and then struggles when thousands arrive. A few hours of design can save months of rework. See Scalability and Reliability for more on these goals.
3. The System Design Process at a Glance
The system design process can be broken down into simple steps. Each step builds on the one before it:
1. Requirement Gathering What must the system do, and how well?
|
v
2. Capacity Estimation How many users, requests and how much data?
|
v
3. High-Level Design Which big components, and how are they connected?
|
v
4. Component Design How does each component work inside?
|
v
5. Data Design How is data stored, read and moved?
|
v
6. Interface (API) Design How do components and clients talk to each other?
|
v
7. Security and Reliability How do we protect the system and survive failures?
|
v
8. Testing and Validation Does the system meet every requirement?
|
+-----> feedback: go back and improve earlier steps
The process is iterative, not a straight line. What you learn while testing or estimating often sends you back to improve an earlier step. The rest of this tutorial explains each step using one example: a URL shortener, a service that turns a long link such as https://example.com/blog/2026/how-to-learn-java into a short one such as https://sho.rt/aZ3k9.
4. Step 1: Requirement Gathering
Before drawing any boxes, understand what the system should do. Requirements come in two kinds:
| Type | Meaning | URL shortener example |
|---|---|---|
| Functional | What the system must do: its features | Create a short link; redirect a short link to the original URL; let links expire |
| Non-functional | How well the system must do it: quality targets | Redirects in under 100 ms; 99.9% availability; short codes must be hard to guess |
Good questions to ask during this step:
- Who are the users, and what are the most important things they need to do?
- How many users are expected now, and in two years?
- How fast must the system respond, and how much downtime is acceptable?
- What must never happen, such as losing data or showing one user another user’s data?
- What is out of scope for the first version?
Write the answers down and agree on them before moving on. See Functional vs Non-Functional Requirements for more detail.
5. Step 2: Capacity Estimation
Capacity estimation, also called back-of-the-envelope calculation, turns requirements into rough numbers. These numbers decide how big the system must be. They do not need to be exact; being within a factor of two or three is enough.
Assumptions
New short links per month 100 million
Reads (redirects) per write 100 : 1
Size of one stored link about 500 bytes
Keep links for 5 years
Write traffic
100,000,000 / (30 days x 24 h x 3,600 s) ~ 40 new links per second
Read traffic
40 x 100 ~ 4,000 redirects per second
Storage
100 million x 12 months x 5 years = 6 billion links
6 billion x 500 bytes ~ 3 TB
These numbers already guide the design: the system is read-heavy, so a cache in front of the database will help a lot, and 3 TB of data fits comfortably in a single well-sized database or a small cluster.
6. Step 3: High-Level Design
High-level design defines the overall architecture of the system: the major components and how they interact. Draw simple boxes and arrows first:
+-----------------+
Users ----> | Load Balancer |
+--------+--------+
|
+-------------+-------------+
| | |
+------v-----+ +-----v------+ +----v-------+
| App Server | | App Server | | App Server | (stateless, easy to add more)
+------+-----+ +-----+------+ +----+-------+
| | |
+------+------+------+------+
| |
+------v------+ +----v-----------+
| Cache | | Database |
| (Redis) | | (primary + |
+-------------+ | read replica) |
+----------------+
| Component | Why it is needed |
|---|---|
| Load balancer | Spreads requests across several application servers and removes failed ones |
| Application servers | Run the shortening and redirect logic; stateless, so more can be added under load |
| Cache | Keeps popular links in memory, so most redirects never reach the database |
| Database | Stores every link permanently; a read replica takes extra read traffic |
Learn more about these building blocks in Load Balancing and What Is Caching.
7. Step 4: Component Design
Component design breaks the system into smaller modules and designs each one in detail: its responsibilities, classes and methods. A clean way to start is to define interfaces first, so each part can be built and tested on its own.
The core of the URL shortener needs two parts: something that turns a number into a short code, and a service that stores and finds links.
package com.javabytechie.shortener;
/**
* Turns a positive number into a short text code using 62 characters
* (0-9, a-z, A-Z). Seven characters can represent over 3.5 trillion links.
*/
public final class Base62Encoder {
private static final String ALPHABET =
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final int BASE = ALPHABET.length();
private Base62Encoder() {
}
public static String encode(long value) {
if (value < 0) {
throw new IllegalArgumentException("Value must not be negative");
}
if (value == 0) {
return String.valueOf(ALPHABET.charAt(0));
}
StringBuilder code = new StringBuilder();
while (value > 0) {
code.append(ALPHABET.charAt((int) (value % BASE)));
value /= BASE;
}
return code.reverse().toString();
}
}
package com.javabytechie.shortener;
import java.util.Optional;
public interface UrlShortenerService {
/** Creates a short code for a long URL and returns it. */
String shorten(String longUrl);
/** Finds the original URL for a short code, if it exists. */
Optional<String> resolve(String code);
}
package com.javabytechie.shortener;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.stereotype.Service;
@Service
public class InMemoryUrlShortenerService implements UrlShortenerService {
private final AtomicLong nextId = new AtomicLong(1_000_000); // start with longer codes
private final Map<String, String> links = new ConcurrentHashMap<>();
@Override
public String shorten(String longUrl) {
String code = Base62Encoder.encode(nextId.getAndIncrement());
links.put(code, longUrl);
return code;
}
@Override
public Optional<String> resolve(String code) {
return Optional.ofNullable(links.get(code));
}
}
This in-memory version is perfect for learning and tests. Because the rest of the code depends only on the UrlShortenerService interface, it can later be replaced by a database-backed version without changing the controller. Note that sequential codes are easy to guess; for the “hard to guess” requirement, a real system would add randomness or use a random ID generator.
8. Step 5: Data Design
Data design decides what is stored, where it is stored and how it flows through the system. For the URL shortener, one table is enough:
CREATE TABLE short_urls (
id BIGINT PRIMARY KEY,
code VARCHAR(10) NOT NULL UNIQUE,
long_url VARCHAR(2048) NOT NULL,
created_at TIMESTAMP NOT NULL,
expires_at TIMESTAMP NULL
);
The UNIQUE constraint on code creates an index, so looking up a link by its code stays fast even with billions of rows. Another key decision in this step is which kind of database to use:
| SQL (relational) | NoSQL (for example key-value) | |
|---|---|---|
| Data shape | Fixed tables with relationships | Flexible documents or simple key-value pairs |
| Strengths | Transactions, joins, strong consistency | Very high scale, simple lookups, easy horizontal growth |
| Good fit for | Orders, payments, user accounts | Sessions, caches, link lookups, activity feeds |
The URL shortener only ever looks links up by code, so a key-value store would work well; a relational database is also fine at this size and easier to start with. Data design also covers the data flow: new links go to the database first, and the cache is filled the first time a link is read. See Consistency for how data stays correct across copies.
9. Step 6: Interface (API) Design
Interface design defines how clients and components communicate. For a web service, this usually means designing the REST API:
| Method and path | Purpose | Success response |
|---|---|---|
POST /api/urls | Create a short link from a long URL | 201 Created with the short URL |
GET /{code} | Redirect to the original URL | 302 Found with a Location header |
package com.javabytechie.shortener;
import java.net.URI;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UrlController {
private static final String BASE_URL = "https://sho.rt/";
private final UrlShortenerService shortener;
public UrlController(UrlShortenerService shortener) {
this.shortener = shortener;
}
@PostMapping("/api/urls")
public ResponseEntity<ShortenResponse> create(@RequestBody ShortenRequest request) {
String code = shortener.shorten(request.longUrl());
return ResponseEntity.status(HttpStatus.CREATED)
.body(new ShortenResponse(BASE_URL + code));
}
@GetMapping("/{code}")
public ResponseEntity<Void> redirect(@PathVariable String code) {
return shortener.resolve(code)
.map(url -> ResponseEntity.status(HttpStatus.FOUND)
.location(URI.create(url))
.<Void>build())
.orElse(ResponseEntity.notFound().build());
}
public record ShortenRequest(String longUrl) {
}
public record ShortenResponse(String shortUrl) {
}
}
# Create a short link
curl -X POST http://localhost:8080/api/urls \
-H "Content-Type: application/json" \
-d '{"longUrl": "https://example.com/blog/2026/how-to-learn-java"}'
# Response: {"shortUrl":"https://sho.rt/4c92"}
# Follow it (shows the 302 redirect)
curl -i http://localhost:8080/4c92
Design APIs to be simple, consistent and stable: clear names, the right HTTP status codes, and versioning when you need to make incompatible changes.
10. Step 7: Security and Reliability
This step adds security measures and designs the system to keep working when parts of it fail:
| Concern | Technique | URL shortener example |
|---|---|---|
| Input safety | Validate all input | Accept only http and https URLs, with a maximum length |
| Abuse prevention | Rate limiting | Limit how many links one client can create per minute |
| Data protection | Encryption in transit | Serve everything over HTTPS |
| No single point of failure | Redundancy | Several app servers behind the load balancer; a database replica |
| Recovering from failure | Backups and failover | Daily database backups; automatic switch to the replica |
| Seeing problems early | Monitoring and alerts | Track error rate and redirect latency, and alert when they rise |
Ask “what happens if this box disappears?” for every component in the high-level diagram, and make sure the answer is acceptable. See Availability to learn how availability targets such as 99.9% are reached.
11. Step 8: Testing and Validation
Testing verifies that the system meets all requirements and performs as expected. Each level of testing checks something different:
| Test type | What it checks | Example |
|---|---|---|
| Unit tests | One class or method on its own | Base62Encoder produces the right codes |
| Integration tests | Components working together | The API saves a link and redirects to it |
| Load tests | Behaviour under expected and peak traffic | 4,000 redirects per second with low latency |
| Failure tests | Recovery when parts fail | Stop one app server and confirm traffic continues |
package com.javabytechie.shortener;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
class UrlShortenerServiceTest {
private final UrlShortenerService service = new InMemoryUrlShortenerService();
@Test
void encodesNumbersAsShortCodes() {
assertThat(Base62Encoder.encode(0)).isEqualTo("0");
assertThat(Base62Encoder.encode(61)).isEqualTo("Z");
assertThat(Base62Encoder.encode(62)).isEqualTo("10");
}
@Test
void resolvesAShortenedUrl() {
String code = service.shorten("https://example.com/page");
assertThat(service.resolve(code)).contains("https://example.com/page");
}
@Test
void returnsEmptyForUnknownCodes() {
assertThat(service.resolve("unknown")).isEmpty();
}
}
Compare test results with the non-functional requirements from step 1. If redirects are slower than 100 ms under load, go back to the design, for example to add more caching. This feedback loop is what makes the process iterative.
12. System Design Methodologies
There are several approaches, or methodologies, used in system design. Some guide how the work is organized (Waterfall, Agile), and others guide how the system is structured (object-oriented, microservices, event-driven). Real projects usually combine one of each kind.
| Methodology | Main idea | Strengths | Watch out for |
|---|---|---|---|
| Waterfall | A sequential approach where each phase is completed before moving to the next | Simple, predictable, well documented | Less flexible when requirements change |
| Agile | Iterative design and development with continuous feedback and improvement | Adapts quickly; users see progress early | Needs discipline to keep the design coherent |
| Object-Oriented Design (OOD) | Designs the system using objects and classes | Modular, reusable, easy to reason about | Deep class hierarchies can become rigid |
| Microservices Architecture | A collection of small, independent services that communicate through APIs | Scales and deploys parts independently | More moving parts to run and monitor |
| Event-Driven Design | Components react to events instead of calling each other directly | Responsive and loosely coupled | Harder to trace the flow of a request |
Read more in Microservices Architecture and Event-Driven Architecture.
13. Waterfall vs Agile
Waterfall: Requirements -> Design -> Build -> Test -> Release (one long pass)
Agile: [Plan -> Design -> Build -> Test -> Review] -> repeat (short cycles,
every 1-4 weeks)
| Aspect | Waterfall | Agile |
|---|---|---|
| Order of work | One phase after another | Short cycles that repeat |
| Changing requirements | Expensive once a phase is finished | Expected and welcomed |
| Feedback from users | Mostly at the end | After every cycle |
| Documentation | Detailed and created up front | Just enough, kept up to date |
| Best for | Fixed, well-understood requirements | Products whose requirements evolve |
14. Choosing the Right Methodology
| Situation | A good fit |
|---|---|
| Requirements are fixed and well known, for example a regulated system | Waterfall |
| Requirements will change as users give feedback | Agile |
| A small team building a new product | A modular monolith using object-oriented design |
| Many teams that must release independently, with parts that scale differently | Microservices |
| Many systems that must react to the same business events in real time | Event-driven design |
There is no single best methodology. Start simple and add complexity only when the requirements demand it; many successful systems began as a well-structured monolith and moved to microservices later. See Monolithic vs Microservices Architecture.
15. Common Design Trade-offs
Every design decision gives something and costs something. Naming the trade-off openly leads to better decisions:
| Trade-off | Choosing the first option | Choosing the second option |
|---|---|---|
| Consistency vs availability | Everyone always sees the latest data | The system keeps answering, even if some data is briefly out of date |
| Latency vs cost | Faster responses with more servers and caches | Lower bills with fewer resources |
| SQL vs NoSQL | Strong transactions and relationships | Easier scaling for simple, large data sets |
| Monolith vs microservices | Simple to build, test and deploy | Independent scaling and team autonomy |
| Simplicity vs flexibility | Easy to understand and change today | Ready for future features, at the cost of complexity now |
16. Best Practices for System Design
| Do | Why |
|---|---|
| Understand user requirements clearly before designing | Designing the wrong system well is still a failure |
| Estimate capacity with rough numbers | Numbers reveal whether you need caching, sharding or replicas |
| Keep the system modular and loosely coupled | Parts can change or be replaced without breaking others |
| Design for scalability and future growth | Adding capacity later should not require a rewrite |
| Consider performance, security and reliability from the start | They are hard to add after the system is built |
| Prefer simple solutions first | Every extra component must be built, run and monitored |
| Document the design decisions for future reference | Future team members need to know why, not just what |
| Review the design with others | A second pair of eyes finds gaps early, when they are cheap to fix |
17. Common Mistakes to Avoid
| Mistake | Problem | Better approach |
|---|---|---|
| Jumping straight to technology choices | Picking tools before knowing the problem | Start with requirements and estimates |
| Ignoring non-functional requirements | The system works, but is slow, fragile or insecure | Write measurable targets for speed, availability and security |
| Over-engineering for imaginary scale | High cost and complexity with no benefit | Design for the next one or two years of expected growth |
| Single points of failure | One broken component stops the whole system | Add redundancy for every critical component |
| No monitoring | Problems are found by users first | Plan metrics, logs and alerts as part of the design |
| Treating the design as finished | The design no longer matches reality | Revisit it as requirements and traffic change |
18. Summary
System design is a structured way to turn requirements into a working, scalable and reliable system. The process moves from requirement gathering and capacity estimation to high-level design, component design, data design and interface design, then adds security and reliability and finishes with testing, with feedback flowing back to improve earlier steps. Methodologies such as Waterfall and Agile shape how the work is organized, while object-oriented design, microservices and event-driven design shape how the system is built.
Understand the requirements clearly, keep the system modular, plan for growth, weigh each trade-off openly and document your decisions, and you will design systems that are easier to build, run and change.
Frequently Asked Questions (FAQ)
Q1. What is system design in simple words?
System design is planning a software system before building it. You decide which parts the system needs, what each part does, how the parts talk to each other, and how the system will stay fast and reliable as more people use it.
Q2. What are the main steps of the system design process?
- Requirement gathering
- Capacity estimation
- High-level design
- Component (low-level) design
- Data design
- Interface (API) design
- Security and reliability
- Testing and validation
The steps are repeated as you learn more, so the process is iterative rather than a straight line.
Q3. What is the difference between functional and non-functional requirements?
Functional requirements describe what the system must do, such as “users can create a short link”. Non-functional requirements describe how well it must do it, such as “redirects take less than 100 ms” or “the service is available 99.9% of the time”.
Q4. Why is capacity estimation important?
Rough numbers for users, requests per second and storage tell you how big the system must be. They show whether you need caching, read replicas or sharding, and stop you from building either too little or far too much. Being within a factor of two or three is good enough.
Q5. How do you calculate requests per second?
Divide the number of requests per day by the number of seconds in a day (about 86,400):
10 million requests per day / 86,400 seconds ~ 116 requests per second
Traffic is rarely even, so multiply by two or three to estimate the peak load the system must handle.
Q6. What is the difference between high-level and low-level design?
High-level design shows the big picture: services, databases, caches, load balancers and how they connect. Low-level design zooms into each component: its classes, interfaces, methods and database tables. High-level design comes first, and low-level design fills in the details.
Q7. Is system design only for large companies?
No. Even a small application benefits from a short design step: agreeing on requirements, choosing a database and drawing a simple diagram. The design for a small system is simply smaller, sometimes just one page.
Q8. Should I choose SQL or NoSQL for my system?
Choose SQL when you need transactions, relationships between data and strong consistency, such as for orders and payments. Choose NoSQL when you need very large scale with simple access patterns, such as key-value lookups, sessions or activity feeds. Many systems use both for different parts.
Q9. What is a single point of failure?
A single point of failure is any one component whose failure stops the whole system, for example a single database server with no copy. Remove it with redundancy: run several app servers behind a load balancer, keep database replicas, and have automatic failover.
Q10. Why should application servers be stateless?
A stateless server keeps no user data in its own memory between requests; that data lives in a database or cache instead. Any server can then handle any request, so you can add or remove servers freely, and losing one server loses no user data.
Q11. When should I add a cache?
Add a cache when the same data is read much more often than it changes, and reading it from the database is slow or expensive. A read-heavy system, such as a URL shortener with 100 reads for every write, benefits greatly. Plan how cached data is updated or expired so users do not see stale data for too long.
Q12. What is the difference between Waterfall and Agile?
Waterfall completes each phase, from requirements to design, build and test, once and in order. Agile repeats short cycles of planning, building and reviewing, adapting the design after each one. Waterfall suits fixed requirements; Agile suits products whose requirements change.
Q13. Is Agile always better than Waterfall?
No. Agile fits most modern products because requirements change, but Waterfall can be the better choice when requirements are fixed and well understood, such as in regulated systems or projects with strict contracts. Some teams combine both: an up-front high-level design followed by Agile delivery.
Q14. What is object-oriented design?
Object-oriented design models a system as objects that combine data and behaviour, such as Order, Customer and Payment. It relies on encapsulation, inheritance and interfaces to keep code modular and reusable. Java is built around this approach.
Q15. When should I use microservices instead of a monolith?
Use microservices when several teams must release independently, or when parts of the system have very different scaling needs. For a new product or a small team, a well-structured monolith is usually faster to build and easier to run; you can split it into services later when the need is real.
Q16. What is event-driven design?
In event-driven design, components announce that something happened, such as “OrderPlaced”, by publishing an event. Other components react to it without being called directly. This keeps components loosely coupled and responsive, often using a message broker such as Apache Kafka.
Q17. What is a design trade-off?
A trade-off is a choice where gaining one quality costs another. For example, keeping data perfectly consistent across servers can reduce availability, and more caching improves speed but adds cost and complexity. Good design names each trade-off and picks the option that best fits the requirements.
Q18. How detailed should a system design document be?
Detailed enough for another engineer to understand and build the system. A good document usually includes the requirements, capacity estimates, a high-level diagram, the main components, the data model, the APIs, key trade-offs and open questions. Keep it short and update it when the design changes.
Q19. How do I practise system design?
Pick familiar products, such as a URL shortener, a chat app or a news feed, and design them step by step: write requirements, estimate capacity, draw the high-level design and explain your trade-offs. Then compare your design with how real systems work, and build small parts in Java to test your ideas.
Q20. What do interviewers look for in a system design interview?
They look at how you think, not for one correct answer:
- Asking clarifying questions about requirements before designing.
- Making reasonable estimates.
- Drawing a clear high-level design and explaining each component.
- Discussing trade-offs, bottlenecks and failure scenarios.
- Communicating clearly and adjusting the design when given new information.