PAUL CHONGSenior Software Engineer

System Design Framework

2026-08-04 · 6 min read

System Design is a critical skill for software engineers, especially when it comes to building scalable and efficient systems. System design is not only about knowing concepts and techniques, but also about tackling problems in a structured approach and managing trade-offs depending on the requirements. This framework provides a structured approach to tackle system design problems, whether in interviews or in real-world scenarios.

The Framework

A structured framework helps you approach complex problems and communicate your thoughts clearly. It ensures that you consider all aspects of the problem and helps your interviewer/colleague understand your thought process. A practical framework to follow is: Requirements, Component Structure, Data Model, API, and Optimizations.

Requirements

Every system design problem starts with understanding the requirements. This includes functional requirements (what the system should do) and non-functional requirements (how the system should perform). It's essential to clarify any ambiguities and ask questions to ensure you have a complete understanding of the problem.

Start with the functional requirements, which define the core features and capabilities of the system. Then, move on to non-functional requirements, which include performance, scalability, reliability, and security considerations. Understanding these requirements will guide your design decisions and help you prioritize trade-offs.

Component Structure

Once you understand the requirements, the next step is to define the component structure of the system. This involves breaking down the system into smaller, manageable components or modules. Each component should have a clear responsibility and interface, allowing for easier maintenance and scalability.

Break the UI into logical parts: what belongs together, what can be reused, and how data flows between components. Consider the interactions between components and how they communicate with each other. This step is crucial for ensuring that the system is modular and can be easily extended or modified in the future.

Typical components to consider include:

  • Server: treat as a black box and assume it exposes some APIs you can call via HTTP, GraphQL, WebSockets, etc.
  • View Layer: responsible for presentation and local interaction state
  • Store/Model Layer: where the application's data and derived state live. Manages cross-cutting data such as user profile, authentication, app layout state, etc.
  • Data Access Layer: the front end interacts with this layer through a typed data access layer that handles fetching, caching, and error management.

Data Model

After defining your component structure, you need to design the data that powers your system. This involves creating a data model that represents the entities and relationships within your system. A well-designed data model ensures that your system can efficiently store, retrieve, and manipulate data. It also makes management state on the client more predictable and efficient.

In this step consider whether to normalize or denormalize your data. Normalized data avoids duplication and makes updates easier, while denormalized data can improve read performance at the cost of increased storage and maintenance burden. Choose the approach that best fits your system's requirements and expected usage patterns.

API

Once you have a defined data model, the next step is to design the APIs that will allow different components of your system to communicate with each other. This involves defining how the client fetches, updates, and synchronizes data with the server.

Server Client Communication

  1. HTTP: RESTful APIs are a common choice for server-client communication. They are stateless, scalable, and easy to understand. Use standard HTTP methods (GET, POST, PUT, DELETE) to perform CRUD operations on your resources.
  2. GraphQL: GraphQL is an alternative to REST that allows clients to request only the data they need. It can reduce over-fetching and under-fetching of data, making it more efficient for certain use cases. However, it can introduce complexity in terms of query optimization and caching.
  3. WebSockets: provide a persistent, bidirectional connection over TCP between the client and server. This is useful for applications that require instant updates, such as chat applications or live data feeds.
  4. Server-Sent Events (SSE): a simpler alternative to WebSockets for one-way real-time updates from the server to the client. It is suitable for applications that need to push updates to clients without requiring a full-duplex connection like live notifications, stock tickers, or streaming data.
  5. Long Polling: a technique where the client makes a request to the server and waits for a response. If the server has no new data, it holds the request open until new data is available or a timeout occurs. This can be used for real-time updates in scenarios where WebSockets or SSE are not feasible.
  6. WebRTC: WebRTC is a peer-to-peer communication protocol over UDP that enables real-time audio, video, and data sharing between browsers and devices. It is commonly used for video conferencing, online gaming, and collaborative applications.

Optimizations

After designing the initial system, it's important to consider optimizations to improve performance, scalability, and reliability. This involves identifying potential bottlenecks and implementing strategies to mitigate them.

Optimizations can be applied at various levels, including the data model, API design, and component structure. Consider implementing caching strategies to reduce the number of requests to the server and improve response times. On the server side, consider load balancing, database indexing, and query optimization to handle increased traffic and ensure efficient data retrieval. For large datasets, consider implementing pagination or infinite scrolling to improve performance and user experience. Additionally, consider implementing monitoring and logging to identify and address performance issues in real-time.

Beyond performance, consider optimizations on reliability and user experience. Implementing retry mechanisms, circuit breakers, and graceful degradation can help ensure that your system remains available and responsive even under adverse conditions. Additionally, consider lazy loading, prefetching, accessibility, i18n, and responsive design to improve the overall usability of your system.

Conclusion

By following this structured framework, you can approach system design problems in a systematic and organized manner. Each system design problem is unique, and the framework provides a flexible approach that can be adapted to different scenarios. Remember to communicate your thought process clearly, justify your design decisions, and consider trade-offs based on the requirements. With practice and experience, you will become more proficient in system design and be able to tackle complex problems with confidence.