
3 System Design Patterns Every Engineer Should Know
If you're preparing for system design interviews or building production systems, understanding core design patterns is essential. These patterns solve recurring problems in distributed systems and help you make better architectural decisions. At Levelop, we break down complex engineering concepts into practical, actionable knowledge.
In this post, we'll explore three fundamental patterns that every software engineer should know: Layered Architecture, Pub/Sub Messaging, and CQRS.
Layered Architecture
Layered architecture is the most common pattern for organizing backend systems. It separates concerns into distinct tiers, each with a clear responsibility.

Each layer only communicates with the layer directly below it. This creates a clean separation of concerns and makes the system easier to test and maintain.
How Layered Architecture Works
The typical layers are: Client (UI), API Gateway, Service (business logic), and Data (persistence). This is the same pattern used by frameworks like Spring Boot, NestJS, and Django.
// Express.js layered structure
const app = express();
// Route -> Controller -> Service -> Repository
app.get("/users/:id", userController.getById);
class UserService {
constructor(private repo: UserRepository) {}
async getById(id: string) {
return this.repo.findById(id);
}
}Pub/Sub Messaging Pattern
The Publish/Subscribe pattern decouples services by introducing a message broker between producers and consumers. Services communicate through topics rather than direct API calls.

Why Pub/Sub Matters for Microservices
In a microservices architecture, direct service-to-service calls create tight coupling. Pub/Sub solves this by routing messages through a broker like Apache Kafka, RabbitMQ, or AWS SNS/SQS.
// Publishing an event
await messageBroker.publish("order.created", {
orderId: order.id,
userId: order.userId,
items: order.items,
total: order.total,
timestamp: new Date().toISOString()
});
// Subscribing to events
messageBroker.subscribe("order.created", async (event) => {
await emailService.sendOrderConfirmation(event.userId, event.orderId);
await analyticsService.trackPurchase(event);
});CQRS — Command Query Responsibility Segregation
CQRS separates read and write operations into different models. The write side handles commands through a domain model, while the read side uses optimized projections for queries.

When CQRS Makes Sense
This pattern shines when read and write workloads have very different scaling requirements. Your write database (PostgreSQL) can be normalized for consistency, while read replicas (Elasticsearch, Redis) are denormalized for fast queries. CQRS is often paired with Event Sourcing for a complete audit trail.
// Command side
class CreateOrderCommand {
async execute(dto: CreateOrderDTO) {
const order = Order.create(dto);
await this.writeRepo.save(order);
await this.eventBus.publish(new OrderCreatedEvent(order));
}
}
// Query side
class OrderQueryHandler {
async getOrderSummary(userId: string) {
return this.readRepo.findByUserId(userId);
}
}When to Use Each Pattern
Choose Layered Architecture for straightforward CRUD applications. Use Pub/Sub when you need event-driven communication and loose coupling. Adopt CQRS when read and write operations have vastly different performance requirements.
In practice, production systems often combine these patterns. You might use layered architecture within each microservice, Pub/Sub for inter-service communication, and CQRS for your highest-traffic read paths.
Key Takeaways
Understanding these patterns gives you a vocabulary for discussing system design and a toolkit for solving distributed systems problems. Start simple, add complexity only when the data access pattern demands it. Stay tuned for more deep dives on the Levelop blog.
Frequently Asked Questions
What is the easiest system design pattern to start with?
Layered Architecture is the simplest and most widely used. Most web frameworks like Express, Django, and Spring Boot use it by default. Start here and add more advanced patterns as your system grows.
Can I use multiple design patterns in one system?
Yes. Production systems almost always combine patterns. A common setup is layered architecture within each service, Pub/Sub for inter-service messaging, and CQRS for high-read endpoints like dashboards or search.
What is the difference between Pub/Sub and a message queue?
A message queue delivers each message to exactly one consumer (point-to-point), while Pub/Sub broadcasts messages to all subscribers of a topic. Use queues for task distribution and Pub/Sub for event notification across multiple services.
When should I avoid CQRS?
Avoid CQRS for simple CRUD applications where reads and writes have similar load. The extra complexity of maintaining separate read and write models is not worth it unless you have clear scaling asymmetry or need an audit trail via Event Sourcing.
How do these patterns help in system design interviews?
Interviewers expect you to pick the right pattern for the constraints given. Mentioning trade-offs like eventual consistency in Pub/Sub or operational overhead in CQRS shows depth beyond just knowing pattern names.
