System Design and Architecture: Not as Complex as You Think
System design and architecture are often framed as advanced concepts that only senior engineers and architects should worry about. But in reality, every software engineer—whether they realize it or not—is constantly making architectural decisions. Writing the actual code is often the harder part.
Two Forces Shaping Architecture / System Design
At a high level, system design can be driven by two different forces:
-
Business Requirements
- Example: Multi-tenancy, security, scalability, compliance.
- These are constraints imposed by the needs of the business and often shape the high-level architecture.
-
Technical Constraints
- Example: Space/time complexity, efficiency, parallelism.
- These are the lower-level concerns that impact performance, maintainability, and correctness.
This is not a MECE (mutually exclusive collectively exhaustive) list, but rather just categories of forces shaping design decisions.
System Design Is Everywhere, Even in Your First Scripts
Many engineers think of “architecture” as something that only applies to large distributed systems, but even the simplest scripts have an implicit design. The first time you wrote a script and decided to store data in a file instead of a database, you made an architectural decision. The first time you structured a function to avoid redundant operations, you were applying design principles.
The only real difference between junior and senior engineers in this regard is experience:
- Senior engineers make better-informed architectural decisions based on past mistakes and knowledge of trade-offs.
- Junior engineers still design their code, but may not realize that they are making architectural decisions.
Single Software vs. Distributed Systems
Another way to slice system design is by scope:
-
Within a Single Piece of Software
- Example: A CLI tool that processes log files efficiently.
- Focus: Memory efficiency, algorithm complexity, separation of concerns.
-
Cross-Software / Distributed Systems
- Example: A backend service calling a database, microservices communicating via queues.
- Focus: Networking overhead, failure handling, consistency models.
Interestingly, a traditional backend application mostly falls under single software design, despite its reliance on a database. The database itself has its own architectural considerations, but the backend-to-database interaction is usually straightforward. True distributed architecture concerns (message queues, replication strategies, caching layers) come into play when scaling across multiple services or handling high availability.
The Illusion of Complexity
The reason system design seems “hard” is that it’s usually taught in the abstract—diagrams of boxes and arrows, trade-offs between CAP theorem constraints, deep dives into microservices vs. monoliths. But at its core, system design is just the logical extension of programming at scale. If you can write good code, you can design good systems.
In fact, writing good code is often harder than designing the system. The system is a set of high-level choices, while the code is where those choices have to be implemented correctly.
Closing Thoughts
System design is not some elite skillset separate from software engineering—it is software engineering. Every time you write code, you make architectural decisions. The best way to get better at system design is to keep writing and refactoring code while thinking about how your decisions scale across different contexts.