Real Software Architecture Works
Section 1: Why System Design Matters
Plenty of applications with clean, well-written code still fail in production. This confuses developers who were taught that good code is the main determinant of software quality. It isn’t — or rather, it’s necessary but nowhere near sufficient. A perfectly written function that queries a database without an index will still bring a system to its knees under real traffic. A beautifully structured API that processes payments synchronously inside a request will still time out and lose transactions the moment a third-party payment provider has a slow day.
The difference between writing code and designing software is the difference between solving a problem correctly in isolation and solving it correctly under the actual conditions the system will face: concurrent users, unreliable networks, partial failures, growing data volumes, and the eventual need for other engineers to safely change what you built. Code quality is a local property — it’s about whether a single function or module does what it’s supposed to do. System design is a global property — it’s about whether all of those correct pieces, combined, behave correctly and reliably as a whole, under real-world load and failure conditions.
This distinction explains a pattern seen repeatedly across the industry: technically talented teams building products that work flawlessly in demos and early testing, then falling over within weeks of real user growth. The typical culprits are almost never “the code was wrong.” They’re things like: a database that was never indexed for the queries it would actually receive at scale, an API that blocked on slow third-party calls instead of processing them asynchronously, a caching layer that was never introduced until response times had already become unbearable, or a monolithic deployment process that made every release a high-risk event instead of a routine one.
System design is the discipline of anticipating these failure modes before they happen, rather than discovering them from a production outage. This article is about how experienced engineers actually think through that process — not as an abstract interview exercise, but as the real, ongoing work of building software that survives contact with real users.
Section 2: From Business Requirements to Technical Architecture
Before any meaningful architecture decision gets made, experienced engineers translate business needs into two distinct categories of requirements — and treat both as equally important, even though only one of them is usually written down explicitly by a product manager.
Functional Requirements
These describe what the system must do: users can create projects, upload files, invite teammates, receive notifications. Functional requirements map fairly directly to product features and are usually well documented, because they’re what stakeholders naturally think and talk about.
Non-Functional Requirements
These describe how the system must behave — and they’re the requirements most likely to be underspecified, assumed, or ignored until they cause a production incident.
| Requirement | What it really means | Consequence of ignoring it |
|---|---|---|
| Performance | How fast must the system respond under realistic, not ideal, conditions | Users abandon slow products; some workflows become unusable at scale |
| Security | What data needs protection, from whom, and against what specific threats | Breaches, data loss, regulatory and reputational damage |
| Availability | What percentage of uptime is genuinely required, and at what cost | Either wasted investment in unneeded redundancy, or unacceptable outages |
| Reliability | Does the system produce correct results consistently, even under partial failure | Silent data corruption or inconsistent behavior that erodes user trust |
| Scalability | Can the system handle 10x or 100x current load without a full redesign | Painful, high-risk emergency rewrites under real production pressure |
| Maintainability | Can new engineers safely understand and modify the system a year from now | Slower feature velocity, more bugs, growing fear of touching old code |
| Cost | What’s the actual infrastructure and operational cost at real expected scale | Runaway cloud bills, or under-provisioned systems that fail under load |
How Experienced Engineers Think Before Coding
The process generally looks like this: clarify the actual scale expected (how many users, how much data, what growth rate), identify which non-functional requirements matter most for this specific product (a healthcare app and a meme-sharing app have very different security and availability needs), and only then start sketching the shape of a system that satisfies both the functional requirements and the non-functional constraints simultaneously.
This is also where trade-offs and technical debt enter the picture honestly. Every architecture decision trades something for something else — development speed for long-term flexibility, cost for redundancy, simplicity for scalability headroom. The mistake isn’t taking on technical debt deliberately; it’s taking it on unknowingly, without a plan to address it once the product’s scale or requirements change.
A practical framework for this stage:
- What does this system absolutely need to do on day one?
- What scale is realistic in year one, and what scale is realistic in year three?
- Which non-functional requirements are non-negotiable for this specific product (security for a fintech app, availability for a real-time collaboration tool)?
- What’s the cost of being wrong about any of these assumptions later, and how expensive would it be to change course?
Section 3: High-Level Architecture
Before diving into any single component, it helps to see the whole system at once. Here’s a high-level architecture representative of most modern SaaS products:
[ Client (Browser / Mobile App) ]
|
[ CDN / Edge Cache ]
|
[ Load Balancer ]
|
[ Reverse Proxy (Nginx) ]
|
[ API Layer / Backend ]
|
-----------------------------------------
| | | |
[ Auth ] [ Cache ] [ Database ] [ File Storage ]
| | | |
| [ Background Workers / Queue ] |
| | |
[ Third-Party Services ] [ Monitoring & Logging ]
(Payments, Email, SMS, etc.)
What each layer is responsible for:
- Client — renders the interface and handles user interaction; should never be trusted to enforce business rules or security on its own
- CDN / edge cache — serves static assets and cacheable content close to the user, reducing latency and load on the origin
- Load balancer — distributes incoming traffic across multiple backend instances for both performance and resilience
- Reverse proxy — handles SSL termination, request routing, and often basic rate limiting before traffic reaches application code
- API layer — the core application logic; validates requests, enforces business rules, and coordinates the components below it
- Authentication — verifies who the user is, typically issuing a token used across subsequent requests
- Database — the durable source of truth for the system’s core data
- Cache — stores frequently accessed data in memory to avoid repeated expensive database queries
- File storage — handles uploaded files and large binary data outside the primary database
- Background workers / queue — process time-consuming or asynchronous tasks without blocking the user-facing request cycle
- Third-party services — external systems the product depends on but doesn’t own, like payment processors or email providers
- Monitoring and logging — gives engineers visibility into what the system is actually doing in production, in real time
Every section that follows zooms into one of these boxes in depth.
Section 4: Database Design
Database decisions are among the most consequential and hardest-to-reverse choices in any system, because real customer data accumulates on top of them quickly.
Core Concepts
Normalization organizes data to minimize redundancy — each fact is stored once, referenced elsewhere. This keeps data consistent but often requires joining multiple tables to answer a query.
Denormalization deliberately duplicates some data to avoid expensive joins, trading storage and update complexity for read performance. Most production systems use a mix: normalized where consistency matters most, denormalized where read performance is critical.
Indexes are data structures that let a database find rows quickly without scanning an entire table. Without the right indexes, a query that’s instant on a small development dataset can take seconds or minutes at production scale.
Primary keys uniquely identify a row; foreign keys enforce relationships between tables, ensuring, for example, that a task can’t reference a project that doesn’t exist.
Transactions and ACID guarantee that a group of database operations either all succeed or all fail together (atomicity), leave the database in a valid state (consistency), don’t interfere with each other when run concurrently (isolation), and persist once committed (durability). This matters enormously for anything involving money, inventory, or any operation where partial completion would corrupt data.
Scaling a Database
| Technique | What it does | When it’s needed |
|---|---|---|
| Replication | Maintains copies of the database across multiple servers | Reliability (failover) and read scaling |
| Read replicas | Replicas dedicated to serving read queries, offloading the primary | High read-to-write ratio applications |
| Partitioning | Splits a large table into smaller, more manageable pieces within one database | Very large individual tables slowing down queries |
| Sharding | Splits data across multiple separate database instances entirely | Data volume or write load exceeds what a single database server can handle |
| Connection pooling | Reuses a limited set of database connections rather than opening a new one per request | Almost always — prevents exhausting database connection limits under load |
Choosing Between PostgreSQL, MySQL, MongoDB, and Redis
| Database | Best fit | Avoid when |
|---|---|---|
| PostgreSQL | Strong relational integrity, complex queries, general-purpose default | Extremely simple, high-throughput key-value access patterns |
| MySQL | Well-understood, widely supported relational needs | Complex analytical queries at very large scale |
| MongoDB | Flexible, evolving document structures; rapid iteration on data shape | Data requiring strong relational integrity and complex joins |
| Redis | Caching, session storage, rate limiting, lightweight queuing | Long-term, primary system-of-record storage |
Common Mistakes Startups Make
- Choosing MongoDB by default without evaluating whether the data is genuinely relational (it usually is, for most SaaS core data)
- Adding indexes only after a production slowdown, rather than designing them alongside the schema
- Running every query against the primary database, even ones that could safely use a read replica
- Never revisiting an early schema decision, even after the product’s actual usage patterns have become clear
Section 5: API Architecture
The API layer is the contract between a system’s frontend and backend — and increasingly, between a system and external consumers.
REST vs. GraphQL vs. gRPC vs. WebSockets
| Style | Best fit | Trade-off |
|---|---|---|
| REST | General-purpose APIs with clear, resource-oriented structure | Can require multiple requests to assemble complex views |
| GraphQL | Clients needing flexible, precisely shaped data in a single request | More complex server-side implementation and caching |
| gRPC | High-performance service-to-service communication, especially internal microservices | Less naturally suited to public, browser-facing APIs |
| WebSockets | Real-time, bidirectional communication (chat, live collaboration, notifications) | More complex connection and state management than request/response APIs |
Authentication and Authorization
Authentication confirms who a user is; authorization confirms what they’re allowed to do. JWT (JSON Web Tokens) are commonly used to carry authentication state statelessly across requests, avoiding server-side session storage — at the cost of making token revocation more complex than with traditional sessions. OAuth is the standard protocol for delegated authorization, commonly used for “log in with Google” style flows and for granting third-party applications limited access to a user’s data without sharing their password.
API Versioning
APIs change over time, but breaking existing clients isn’t acceptable once an API is in real use. Versioning strategies — URL-based (/v1/, /v2/), header-based, or additive-only changes — exist to let an API evolve without breaking every consumer depending on its current shape.
Rate Limiting
Rate limiting caps how many requests a client can make in a given time window, protecting the system from abuse, accidental overload, and runaway client bugs.
Pagination
Returning large result sets all at once is both slow and wasteful. Pagination — commonly offset-based or cursor-based — returns data in manageable chunks, with cursor-based pagination generally preferred at scale for its consistency under concurrent writes.
Validation and Idempotency
Validation rejects malformed or malicious input before it reaches business logic, preventing entire categories of bugs and security vulnerabilities. Idempotency ensures that repeating the same request (due to a retry after a network failure, for example) doesn’t cause duplicate effects — critical for anything involving payments or other operations where “it happened twice” is a real, damaging failure mode.
Section 6: Caching
Caching is one of the highest-leverage techniques in system design: it can turn an expensive, slow operation into a near-instant one, simply by remembering the answer instead of recomputing it every time.
Layers of Caching
| Layer | What it caches | Typical use case |
|---|---|---|
| Browser cache | Static assets, some API responses | Reducing repeat network requests for unchanged content |
| CDN | Static files, sometimes full page responses | Serving content close to the user geographically |
| Application cache (Redis) | Frequently accessed data, computed results, session data | Avoiding repeated expensive database queries or computations |
| Database cache | Query results at the database engine level | Automatic performance boost for repeated identical queries |
Cache Strategies
- Cache-aside (lazy loading): the application checks the cache first; on a miss, it queries the database and populates the cache for next time. Simple and widely used, but the first request after a cache miss is always slower.
- Write-through: data is written to the cache and the database simultaneously, keeping them in sync at write time, at the cost of slightly slower writes.
- Write-back: data is written to the cache first and persisted to the database asynchronously later, improving write speed at the cost of a small risk window if the cache fails before the write completes.
Cache Invalidation and TTL
Cache invalidation — deciding when cached data is no longer valid — is famously one of the hardest problems in computer science, precisely because stale data can cause subtle, confusing bugs. TTL (time to live) is the simplest and most common approach: cached data automatically expires after a set duration, trading perfect freshness for implementation simplicity.
A real production example: A SaaS product’s dashboard aggregates data across dozens of database queries. Computing this fresh on every page load would be prohibitively slow at scale. Instead, the result is cached with a short TTL (say, 60 seconds), meaning most users see a response in milliseconds, and the underlying data is never more than a minute out of date — an acceptable trade-off for a dashboard, though it would be unacceptable for, say, a real-time payment balance.
Section 7: Background Jobs & Queues
Not every operation belongs inside the request-response cycle a user is actively waiting on. Some work is slow, some depends on unreliable third parties, and some simply doesn’t need to block the user from moving on.
Why Everything Should Not Happen Inside an API Request
If sending a confirmation email happens synchronously during a signup request, a slow or temporarily down email provider directly degrades the signup experience — or breaks it entirely. Moving that work to a background job means the user gets an immediate response, and the email gets sent independently, with retry logic if it fails.
Core Components
- Cron jobs run scheduled, recurring tasks — nightly reports, periodic cleanup, subscription renewal checks.
- Message queues (RabbitMQ, Kafka, or lighter tools like BullMQ) hold jobs to be processed asynchronously, decoupling the part of the system that creates work from the part that executes it.
- Workers are processes that pull jobs from a queue and execute them, independently of the web application servers handling live user requests.
Common Use Cases for Background Processing
| Use case | Why it belongs in a queue |
|---|---|
| Email queues | Third-party email providers can be slow or temporarily unavailable; shouldn’t block user-facing requests |
| Image/video processing | Resizing, transcoding, or analyzing media is computationally expensive and time-consuming |
| Payment processing | Often involves calling external providers with variable latency and requires reliable retry handling |
| Notification systems | Sending to multiple channels (email, push, SMS) shouldn’t slow down the action that triggered them |
RabbitMQ vs. Kafka: RabbitMQ is generally favored for traditional task queuing with complex routing needs. Kafka is generally favored for high-throughput event streaming, where large volumes of events need to be processed in order and potentially replayed later — a meaningfully different use case from simple background job processing.
Section 8: Scaling Applications
Vertical vs. Horizontal Scaling
Vertical scaling means making a single server more powerful (more CPU, more memory). It’s simple but has a hard ceiling and creates a single point of failure. Horizontal scaling means adding more servers to share the load. It’s more complex to implement correctly but scales further and improves resilience, since no single server’s failure takes down the entire system.
The Path to Horizontal Scale
[ Load Balancer ]
| | |
[ App 1 ] [ App 2 ] [ App 3 ] <- Autoscaling group
| | |
[ Shared Database / Cache ]
For this to work, application servers generally need to be stateless — meaning any server can handle any request, because no user-specific data is stored only in that one server’s memory. Session management in a horizontally scaled system typically moves session data into a shared store (like Redis) rather than local server memory, so a user’s session survives even if their next request lands on a different server.
Containers and Orchestration
Docker packages an application with everything it needs to run consistently across environments. Kubernetes orchestrates containers at scale: deploying them, restarting failed instances automatically, and managing autoscaling — automatically adjusting the number of running instances based on real-time demand. This kind of elastic scaling is only practical because most modern applications run on cloud infrastructure delivered as a service, where compute capacity can be requested and released on demand rather than purchased and provisioned manually in advance.
Health Checks and Safe Deployments
Health checks let a load balancer or orchestrator detect an unhealthy instance and stop routing traffic to it automatically. Blue-green deployment runs a new version alongside the old one, switching traffic over only once the new version is confirmed healthy — making rollback nearly instant if something’s wrong. Rolling deployment gradually replaces old instances with new ones, avoiding the need to run two full environments simultaneously, at the cost of a brief period where both versions run side by side.
Section 9: Observability
Writing correct code is not the same as knowing whether that code is behaving correctly in production, under real conditions, right now. Observability is what closes that gap.
The Three Pillars
| Pillar | What it captures | Primary tools |
|---|---|---|
| Logging | Discrete events and their context, useful for debugging specific incidents | Structured logs, cloud logging services |
| Metrics | Numerical measurements over time (response times, error rates, resource usage) | Prometheus, Grafana |
| Tracing | The full path of a single request as it moves through multiple services | OpenTelemetry, distributed tracing tools |
Prometheus collects and stores metrics over time. Grafana visualizes those metrics in dashboards, making system health visible at a glance rather than requiring someone to query raw data manually. Sentry and similar tools capture and alert on application errors as they happen, often with enough context (stack traces, affected users) to diagnose an issue quickly. OpenTelemetry provides a standardized way to instrument applications for tracing and metrics, reducing vendor lock-in to any single monitoring platform.
Alerts and Incident Response
Monitoring without alerting only helps after someone happens to look at a dashboard. Well-designed alerting notifies the right people automatically when a metric crosses a meaningful threshold — and just as importantly, avoids alerting so aggressively that engineers start ignoring alerts altogether (“alert fatigue”), which defeats the entire purpose.
Why monitoring is as important as coding: A system with perfect code but no observability is a system where problems are discovered by customers first, and diagnosed by guesswork. A system with good observability turns “something is wrong somewhere” into “this specific service’s error rate spiked at this specific time, likely caused by this specific deploy” — the difference between a five-minute fix and a multi-hour outage.
Section 10: Security by Design
Security cannot be bolted onto a system after the fact and be expected to work reliably — it needs to be a design consideration from the start, informed by the same core cybersecurity fundamentals that apply across any networked system, not just SaaS products specifically.
Core Concepts
- Authentication and authorization, covered in Section 5, are the foundation: knowing who a user is, and precisely what they’re allowed to do.
- Encryption protects data both in transit (via HTTPS/TLS) and at rest (encrypting stored data so a database breach doesn’t automatically expose everything in plain text).
- Secrets management keeps credentials, API keys, and other sensitive configuration out of source code, typically using dedicated secrets management tools rather than environment variables checked into version control by accident.
Common Vulnerability Classes
| Threat | What it is | Primary defense |
|---|---|---|
| CSRF (Cross-Site Request Forgery) | Tricking a logged-in user’s browser into making unwanted requests | Anti-CSRF tokens, same-site cookie policies |
| XSS (Cross-Site Scripting) | Injecting malicious scripts into content viewed by other users | Output encoding, content security policies |
| SQL Injection | Manipulating database queries through unsanitized input | Parameterized queries, input validation |
| SSRF (Server-Side Request Forgery) | Tricking a server into making requests to unintended internal resources | Strict allowlisting of outbound destinations, network segmentation |
Operational Security Practices
- Rate limiting, covered in Section 5, also serves as a security control, slowing down brute-force and abuse attempts.
- Audit logs record who did what, when — essential for investigating incidents after the fact and often a compliance requirement.
- Backups, tested regularly, ensure data can actually be restored, not just that a backup file technically exists.
- Disaster recovery planning defines how quickly a system can recover from a serious failure, and how much data loss, if any, is acceptable.
- Zero trust concepts assume no request should be implicitly trusted just because it originates from inside a company’s network, verifying identity and authorization on every request rather than relying on network location as a proxy for trust.
Security checklist:
- All traffic encrypted via HTTPS/TLS
- Secrets stored in a dedicated secrets manager, never in source code
- Input validated and queries parameterized to prevent injection attacks
- Rate limiting in place on authentication and other sensitive endpoints
- Audit logging enabled for sensitive actions
- Backups tested for actual restorability, not just existence
- Disaster recovery plan documented, with defined acceptable downtime and data loss thresholds
Section 11: Real SaaS Architecture Case Study
Consider a fictional project management SaaS product and trace a single request through the full production system.
User creates a new task
|
v
[ DNS ]
|
v
[ Cloudflare ] (CDN, DDoS protection)
|
v
[ Load Balancer ]
|
v
[ Nginx ] (reverse proxy, SSL termination)
|
v
[ Backend API ]
|
|------> [ Redis ] (session/permission lookup)
|
|------> [ PostgreSQL ] (writes the new task record)
|
|------> [ Object Storage ] (if a file was attached)
|
|------> [ Queue ] (publishes a "task created" event)
|
v
[ Email Service ] (notifies the assigned teammate)
|
v
[ Monitoring ] (logs and metrics captured throughout)
Why each component exists:
- DNS resolves the domain to the correct infrastructure.
- Cloudflare absorbs malicious traffic and serves cacheable content close to the user.
- The load balancer ensures no single backend instance becomes a bottleneck or single point of failure.
- Nginx handles SSL termination and forwards requests internally.
- The backend API validates the request, checks permissions (via a fast Redis lookup rather than hitting the database repeatedly), and executes the core business logic.
- PostgreSQL persists the task as the durable source of truth.
- Object storage handles any attached files without bloating the primary database.
- The queue decouples notification delivery from the user’s request, so a slow email provider never slows down task creation itself.
- Monitoring captures what happened throughout, enabling engineers to diagnose issues and understand real system behavior over time.
This is the same underlying pattern that shows up across nearly every mature SaaS product, regardless of specific industry — the components exist because each one solves a specific, recurring production problem: performance, reliability, responsiveness, and visibility.
Section 12: System Design Mistakes Beginners Make
| Mistake | Consequence | Better alternative |
|---|---|---|
| Using MongoDB for everything by default | Fighting the database for relational data it wasn’t designed to handle well | Choose the database type based on the actual shape of the data |
| Ignoring indexes until performance degrades | Queries that were instant in development become painfully slow at real scale | Design indexes alongside the schema, based on expected query patterns |
| No caching layer | Every request hits the database directly, limiting scalability early | Introduce caching deliberately for expensive or frequently repeated queries |
| Monolithic APIs without planning | Tightly coupled code that becomes increasingly risky and slow to change | Structure code with clear internal boundaries, even within a single deployable service |
| Hardcoding secrets | Credentials leaked through source control, a serious and common security incident | Use a dedicated secrets management approach from day one |
| No logging | Debugging production issues becomes guesswork | Implement structured logging early, before it’s urgently needed |
| No monitoring | Problems are discovered by customers, not by the engineering team | Set up basic metrics and alerting before the first real users arrive |
| Blocking requests on slow operations | Poor user experience and cascading failures when a dependency is slow | Move non-essential, slow work to background jobs |
| Poor database design | Painful, risky migrations once the product has real customer data | Invest real thought into data modeling before the schema is locked in by production data |
The pattern across nearly every item in this table: these mistakes are invisible at small scale and become expensive precisely when a product starts succeeding — which is exactly the wrong time to discover them.
Section 13: How AI Helps System Design
AI tools — Claude, ChatGPT, Cursor, Codex, GitHub Copilot, Gemini, and Windsurf — have become genuinely useful in the system design process, though their role is best understood as an accelerant for specific tasks rather than a substitute for the judgment this entire article has been describing.
Where AI tools genuinely help:
- Architecture brainstorming — surfacing options and trade-offs quickly, which a human engineer then evaluates against the specific product’s real constraints
- Documentation — drafting architecture decision records and technical documentation faster
- API design — scaffolding consistent, well-structured endpoint definitions from a description
- Refactoring — assisting with large, mechanical refactors across a codebase
- Threat modeling — flagging common, well-known vulnerability patterns for a human to verify and prioritize
- Performance review — suggesting likely causes of a slow query or bottleneck, faster than manual investigation alone
- Debugging — helping narrow down the likely source of an issue in unfamiliar code
Where human engineering judgment remains essential: deciding which of several valid architectures actually fits a specific team’s scale, budget, and hiring plan; understanding context an AI tool has no access to, like a company’s specific compliance requirements or a past incident that shaped a current design decision; and taking accountability for a system’s reliability in a way no tool can. Part of using these tools well comes from understanding how they actually generate their output — pattern completion based on training data — which explains why they’re excellent at well-established, widely documented architecture patterns, and less reliable for the genuinely novel trade-offs a specific product might require. Teams evaluating which specific AI tools fit their engineering workflow should treat that evaluation as a separate, ongoing decision from the architecture itself — tools change quickly; sound system design principles don’t.
Section 14: Learning Roadmap
| Focus area | Suggested learning order |
|---|---|
| Backend engineering | Master one language and framework deeply, then study API design, authentication, and background job processing |
| Databases | Learn relational modeling and indexing thoroughly before branching into NoSQL, replication, and sharding concepts |
| Cloud | Understand core compute, storage, and networking concepts before diving into any single provider’s specific tooling |
| DevOps | Start with containers (Docker), then CI/CD pipelines, then orchestration (Kubernetes) once the earlier fundamentals are solid |
| Architecture | Study real system design case studies and practice designing systems for products you understand well, not abstract exercises alone |
| Security | Learn the core vulnerability classes in Section 10 deeply before broadening into compliance and advanced threat modeling |
| Performance | Learn to profile and measure before optimizing — most performance intuition without measurement turns out to be wrong |
| System design overall | Build and operate a real, even small, production system end-to-end; abstract study without hands-on experience rarely builds durable judgment |
A realistic order for someone starting from a solid coding foundation: databases and API design first, since nearly everything else builds on them; then caching and background jobs, since they solve the most common early performance and reliability problems; then scaling and observability together, since they’re deeply connected in practice; then security, woven throughout rather than treated as a final step; and finally, cloud and DevOps tooling, which matters most once there’s a real system worth deploying and operating reliably.
Glossary
| Term | Definition |
|---|---|
| Idempotency | The property that repeating an operation produces the same result as performing it once |
| Statelessness | An application design where no server holds user-specific state that only it has access to |
| Sharding | Splitting data across multiple separate database instances to scale beyond a single server’s capacity |
| TTL (Time to Live) | The duration cached data remains valid before it’s considered stale and refreshed |
| Zero trust | A security model that verifies every request’s identity and authorization, regardless of its network origin |
| Observability | The ability to understand a system’s internal state from its external outputs — logs, metrics, and traces |
Frequently Asked Questions
Do I need to learn system design if I only work on small projects? The core principles — caching, background processing, sensible database design — pay off even at small scale, since they’re often easier to build in from the start than to retrofit later once real usage patterns exist.
Should every project use microservices? No. Most successful products start as a well-structured monolith and only move toward microservices once the scale and team size genuinely justify the added operational complexity.
Is REST outdated compared to GraphQL? No — they solve different problems well. REST remains an excellent default for many APIs; GraphQL shines specifically when clients need flexible, precisely shaped data from a single request.
How do I know when my application actually needs caching? When the same expensive query or computation is being repeated frequently with unchanged results — a slow dashboard or a frequently accessed but rarely updated resource are classic signals.
What’s the single most common system design mistake in early-stage startups? Underinvesting in database design and indexing early, since the consequences are invisible at small scale and become painful precisely when a product starts growing.
Is Kubernetes necessary for a new SaaS product? Usually not immediately. It adds real operational complexity that’s only justified once a system has enough scale and service count to need sophisticated orchestration.
How much security work is really necessary before launch? The core fundamentals — encrypted traffic, proper secrets management, input validation, and basic rate limiting — should be non-negotiable from day one, since retrofitting them after an incident is far more costly than building them in from the start.
Can AI tools replace the need to learn system design? No. AI tools accelerate specific tasks within the process, but the judgment to evaluate trade-offs for a specific product’s real constraints still requires understanding these principles directly.
What’s the difference between monitoring and logging? Logging captures discrete events and their context, useful for investigating specific incidents. Monitoring captures aggregate, ongoing metrics about system health, useful for spotting trends and triggering alerts before a full investigation is needed.
How do I practice system design without working at a large company? Design and, ideally, build real systems for products you understand well, even small personal projects — deliberately introducing constraints like “assume 100,000 users” forces the same trade-off thinking real production systems require.
Key Takeaways
- Good code and good architecture are related but distinct — a system can be built entirely of well-written code and still fail under real production conditions if it wasn’t designed to handle them.
- Non-functional requirements — performance, security, availability, scalability — deserve the same deliberate attention as functional features, even though they’re rarely written down as explicitly.
- Database design decisions are among the most consequential and hardest to reverse; they deserve serious upfront thought, not default choices made out of habit.
- Caching and background job processing are two of the highest-leverage techniques available for improving both performance and reliability, and they’re valuable to introduce well before they become urgently necessary.
- Observability isn’t optional polish — it’s what turns “something is broken” into a fast, targeted fix instead of a prolonged, stressful investigation.
- Security must be designed in from the start; retrofitting it after an incident is dramatically more costly than building it in deliberately from day one.
- AI tools meaningfully accelerate specific parts of the system design process, but the underlying judgment — evaluating trade-offs for a specific product’s real constraints — remains a human responsibility that these principles exist to build.