No history yet

System Design Fundamentals

From Code to Architecture

Writing code is one thing; designing a system that can serve millions of users is another. As applications grow, the simple structures that work for a small project begin to break down. System design is the art of planning the architecture of a software application to be robust, scalable, and maintainable. The first major decision you'll face is how to structure the application itself.

Imagine you're building an e-commerce platform. A monolithic architecture would be like building a single, massive warehouse that handles everything: inventory, customer accounts, payments, and shipping. All the code is in one large codebase, deployed as a single unit. It's simple to develop and deploy initially, but making a small change to the payment system requires redeploying the entire application. As the warehouse gets bigger, it becomes difficult to manage and upgrade.

The alternative is a microservices architecture, which is like a business park. Instead of one giant building, you have several smaller, specialized buildings. One handles inventory, another handles payments, and a third manages user profiles. Each service is independent, with its own codebase and database. You can update the payment service without touching the inventory service, making the system more flexible and easier to scale.

Choosing between monolithic and microservices isn't about right or wrong. It's about trade-offs between initial simplicity and long-term flexibility.

Handling More Traffic

Whether you choose a monolith or microservices, a successful application will eventually face more traffic than a single server can handle. This is where scaling comes in. There are two primary ways to scale a system.

Vertical scaling means making your server more powerful. It's like replacing a delivery van with a massive lorry. You add more CPU, RAM, or faster storage to the existing machine. This approach is simple but has limits. Eventually, you can't buy a bigger machine, and the cost increases exponentially.

Horizontal scaling means adding more servers to share the load. Instead of one big lorry, you use a fleet of delivery vans. This is more complex to manage because you need a way to distribute traffic between them, but it's far more flexible and can scale almost indefinitely. This is the foundation of most large-scale web applications.

When you scale horizontally, you need a traffic cop. That's the job of a load balancer. It's a server that sits in front of your application servers and distributes incoming network traffic across them. This prevents any single server from becoming overwhelmed and improves the application's availability. If one server fails, the load balancer simply stops sending traffic to it and redirects requests to the healthy servers.

Architectural Patterns

Organising code within a server is just as important as organising the servers themselves. Several common patterns help structure applications.

Multi-tier architecture separates an application into logical layers. A typical 3-tier architecture includes:

  1. Presentation Tier: The user interface (e.g., a website or mobile app).
  2. Logic Tier: The backend that processes user requests and business logic (e.g., a server running Python or Java).
  3. Data Tier: The database that stores application data.

This separation of concerns makes the system easier to develop and maintain, as each tier can be managed independently.

Lesson image

The Model-View-Controller (MVC) pattern is a popular way to organise the logic tier. It divides the code into three interconnected parts:

  • Model: Manages the data and business logic. It's the 'brain' of the application.
  • View: Displays the data from the model to the user. It's the user interface.
  • Controller: Takes user input and tells the model and view what to do.

This pattern helps keep the user interface code separate from the business logic, improving code organisation.

The Realities of Distributed Systems

When you move from a single server to a distributed system with multiple servers (as in horizontal scaling or microservices), you encounter a fundamental trade-off described by the (Consistency, Availability, and Partition Tolerance).

The theorem states that a distributed system can only provide two of these three guarantees at the same time:

  • Consistency: Every read receives the most recent write or an error. All nodes in the system have the same data at the same time.
  • Availability: Every request receives a (non-error) response, without the guarantee that it contains the most recent write. The system is always operational.
  • Partition Tolerance: The system continues to operate despite an arbitrary number of messages being dropped (or delayed) by the network between nodes.

Since network failures (partitions) are a given in the real world, you must design for them. This means you are forced to choose between consistency and availability. Do you show a user potentially stale data (prioritising availability), or do you return an error until the data is consistent (prioritising consistency)? This choice is a core challenge in system design.

Designing scalable, maintainable, and high-performance systems takes more than just clean code—it requires a strong grasp of architecture, trade-offs, and real-world constraints.

These concepts form the building blocks for designing complex, large-scale applications. Let's test your understanding.

Quiz Questions 1/6

An e-commerce company finds it slow and risky to update its single, large application, as a small change requires redeploying the entire system. Which architectural style would best address this by breaking the application into smaller, independent parts?

Quiz Questions 2/6

A fast-growing video streaming service needs to handle more users. The team decides to upgrade their existing server by adding more powerful CPUs and increasing its RAM. What is this scaling strategy called?

Understanding these architectural patterns and trade-offs is the first step towards thinking like a system architect.