00:00

System Design Process and Methodologies

Last updated:

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?

BenefitWhat it means
ScalabilityHelps the system handle growth in users and data efficiently
PerformanceEnsures the system responds quickly and works smoothly under load
ReliabilityReduces the chance of system failures or downtime
MaintainabilityMakes future updates and changes easier to implement
SecurityProtects data and users by planning safeguards from the start
Lower costFixing 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:

The design process
  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:

TypeMeaningURL shortener example
FunctionalWhat the system must do: its featuresCreate a short link; redirect a short link to the original URL; let links expire
Non-functionalHow well the system must do it: quality targetsRedirects 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.

Estimates for the URL shortener
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:

High-level design of the URL shortener
                 +-----------------+
   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) |
                            +----------------+
ComponentWhy it is needed
Load balancerSpreads requests across several application servers and removes failed ones
Application serversRun the shortening and redirect logic; stateless, so more can be added under load
CacheKeeps popular links in memory, so most redirects never reach the database
DatabaseStores 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.

Base62Encoder.java
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();
    }
}
UrlShortenerService.java
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);
}
InMemoryUrlShortenerService.java
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:

schema.sql
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 shapeFixed tables with relationshipsFlexible documents or simple key-value pairs
StrengthsTransactions, joins, strong consistencyVery high scale, simple lookups, easy horizontal growth
Good fit forOrders, payments, user accountsSessions, 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 pathPurposeSuccess response
POST /api/urlsCreate a short link from a long URL201 Created with the short URL
GET /{code}Redirect to the original URL302 Found with a Location header
UrlController.java
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) {
    }
}
Trying the API
# 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:

ConcernTechniqueURL shortener example
Input safetyValidate all inputAccept only http and https URLs, with a maximum length
Abuse preventionRate limitingLimit how many links one client can create per minute
Data protectionEncryption in transitServe everything over HTTPS
No single point of failureRedundancySeveral app servers behind the load balancer; a database replica
Recovering from failureBackups and failoverDaily database backups; automatic switch to the replica
Seeing problems earlyMonitoring and alertsTrack 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 typeWhat it checksExample
Unit testsOne class or method on its ownBase62Encoder produces the right codes
Integration testsComponents working togetherThe API saves a link and redirects to it
Load testsBehaviour under expected and peak traffic4,000 redirects per second with low latency
Failure testsRecovery when parts failStop one app server and confirm traffic continues
UrlShortenerServiceTest.java
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.

MethodologyMain ideaStrengthsWatch out for
WaterfallA sequential approach where each phase is completed before moving to the nextSimple, predictable, well documentedLess flexible when requirements change
AgileIterative design and development with continuous feedback and improvementAdapts quickly; users see progress earlyNeeds discipline to keep the design coherent
Object-Oriented Design (OOD)Designs the system using objects and classesModular, reusable, easy to reason aboutDeep class hierarchies can become rigid
Microservices ArchitectureA collection of small, independent services that communicate through APIsScales and deploys parts independentlyMore moving parts to run and monitor
Event-Driven DesignComponents react to events instead of calling each other directlyResponsive and loosely coupledHarder to trace the flow of a request

Read more in Microservices Architecture and Event-Driven Architecture.

13. Waterfall vs Agile

How the two approaches flow
Waterfall:  Requirements -> Design -> Build -> Test -> Release      (one long pass)

Agile:      [Plan -> Design -> Build -> Test -> Review] -> repeat    (short cycles,
                                                                       every 1-4 weeks)
AspectWaterfallAgile
Order of workOne phase after anotherShort cycles that repeat
Changing requirementsExpensive once a phase is finishedExpected and welcomed
Feedback from usersMostly at the endAfter every cycle
DocumentationDetailed and created up frontJust enough, kept up to date
Best forFixed, well-understood requirementsProducts whose requirements evolve

14. Choosing the Right Methodology

SituationA good fit
Requirements are fixed and well known, for example a regulated systemWaterfall
Requirements will change as users give feedbackAgile
A small team building a new productA modular monolith using object-oriented design
Many teams that must release independently, with parts that scale differentlyMicroservices
Many systems that must react to the same business events in real timeEvent-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-offChoosing the first optionChoosing the second option
Consistency vs availabilityEveryone always sees the latest dataThe system keeps answering, even if some data is briefly out of date
Latency vs costFaster responses with more servers and cachesLower bills with fewer resources
SQL vs NoSQLStrong transactions and relationshipsEasier scaling for simple, large data sets
Monolith vs microservicesSimple to build, test and deployIndependent scaling and team autonomy
Simplicity vs flexibilityEasy to understand and change todayReady for future features, at the cost of complexity now

16. Best Practices for System Design

DoWhy
Understand user requirements clearly before designingDesigning the wrong system well is still a failure
Estimate capacity with rough numbersNumbers reveal whether you need caching, sharding or replicas
Keep the system modular and loosely coupledParts can change or be replaced without breaking others
Design for scalability and future growthAdding capacity later should not require a rewrite
Consider performance, security and reliability from the startThey are hard to add after the system is built
Prefer simple solutions firstEvery extra component must be built, run and monitored
Document the design decisions for future referenceFuture team members need to know why, not just what
Review the design with othersA second pair of eyes finds gaps early, when they are cheap to fix

17. Common Mistakes to Avoid

MistakeProblemBetter approach
Jumping straight to technology choicesPicking tools before knowing the problemStart with requirements and estimates
Ignoring non-functional requirementsThe system works, but is slow, fragile or insecureWrite measurable targets for speed, availability and security
Over-engineering for imaginary scaleHigh cost and complexity with no benefitDesign for the next one or two years of expected growth
Single points of failureOne broken component stops the whole systemAdd redundancy for every critical component
No monitoringProblems are found by users firstPlan metrics, logs and alerts as part of the design
Treating the design as finishedThe design no longer matches realityRevisit 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.