Architectural Design Patterns — Overview
MVC, MVVM, layered, microservices, event-driven, hexagonal. The patterns that organise whole systems, not just classes.
Summary#
Gang-of-Four patterns organise classes. Architectural patterns organise whole systems — they tell you which modules exist, who depends on whom, and where the seams between subsystems live. The unit of reuse is no longer a class but a layer, a service, a bounded context.
Six patterns dominate everyday architecture work, and most production systems are a mix of two or three of them: MVC for the UI–domain split, MVVM for declarative front-ends, Layered for the classic tiered server, Microservices for independently deployed services, Event-driven for asynchronous integration, and Hexagonal (Ports and Adapters) for keeping the domain core free of framework concerns. The names overlap; pick a vocabulary and use it consistently.
The interview test is not “name them” — it is “given these forces (team size, deployability, latency, change frequency), which one would you reach for, and which one would you explicitly reject?” That question is what this overview prepares you for.
Why it matters#
Class-level patterns shape a single component. Architectural patterns shape the shipping unit — what gets deployed together, what fails together, what is owned by which team. A wrong architectural choice costs months; a wrong class choice costs an afternoon.
Three concrete reasons interviewers (and seniors) care:
- Team topology lives or dies by it. A four-engineer team that picks microservices spends 80% of its capacity on platform work. A 200-engineer team on a single monolith spends 80% of its capacity in merge conflicts. The architecture must match the organisation that runs it (Conway’s Law in the room).
- Change locality scales. When the product roadmap pivots, the right architecture confines the change to a small set of modules. The wrong one cascades through the whole stack.
- Failure isolation depends on it. Whether a slow downstream call brings the user-facing page to its knees is decided at the architectural seam, not in any one class.
How it works#
The thread running through every architectural pattern is the dependency direction. The patterns differ chiefly in which way the arrows point between the parts of the system. MVC points UI at domain; layered points top-down through tiers; hexagonal points everything inward at the domain core; event-driven inverts the arrows entirely by making producers ignorant of consumers.
The simplest example is MVC — it carries enough load to be useful on its own and is the easiest to sketch in code. A minimal Java sketch:
// Model — the domain. Knows nothing of UI.public final class Counter { private int value = 0; public int value() { return value; } public void increment() { value++; }}
// View — the presentation. Knows the model's read API only.public final class CounterView { public void render(Counter c) { System.out.println("Count: " + c.value()); }}
// Controller — the glue. Receives input, mutates the model, asks the view to redraw.public final class CounterController { private final Counter model; private final CounterView view; public CounterController(Counter m, CounterView v) { this.model = m; this.view = v; }
public void onIncrementClicked() { model.increment(); view.render(model); }}Three things to notice:
- The model is framework-free. It has no
JFrame, no HTTP, no DOM. It could be reused in a CLI tool, a unit test, or a server endpoint. - The view depends on the model, not the other way around. The model never imports the view; it does not know who, if anyone, is rendering it.
- The controller is the only place the two meet. Replacing the view with an HTML renderer or a mobile screen does not touch the model.
That triad — UI in one place, domain in another, glue named explicitly — is the bone structure that the other architectural patterns vary. Layered architecture adds more strata; hexagonal inverts the dependency on infrastructure; microservices fork each module into its own deployable; event-driven turns the controller arrow into a published message.
Variants and trade-offs#
The six patterns most worth keeping vocabulary for:
MVC — Model · View · Controller#
Originated in Smalltalk-80 for desktop GUIs and now a structural default for web frameworks (Spring MVC, Rails, Django).
- Shape. Model holds data and rules. View renders the model. Controller handles input and routes it to the model.
- Best for. UI applications where the domain is non-trivial and the presentation can change independently. Server-side web apps are the canonical fit.
- Watch out for. The “fat controller” anti-pattern — controllers accreting business logic that belongs in the model. The fix is the same as the SRP refactor: split out domain services and keep the controller as a coordinator.
MVVM — Model · View · ViewModel#
A descendant of MVC built for declarative, data-binding UIs (WPF, modern Android with Jetpack, SwiftUI, Vue, React with hooks).
- Shape. The ViewModel exposes observable state shaped exactly for the View. The View binds to ViewModel properties and is otherwise dumb. The Model is the same domain layer as in MVC.
- Best for. UIs where binding eliminates the imperative
view.render(model)call entirely. The framework wires the dependency. - Watch out for. ViewModel bloat — when the view-shaping logic outgrows what one class can hold. Split per-screen, not per-app.
Layered (n-tier)#
Presentation → Application → Domain → Infrastructure, stacked like sediment.
- Shape. Each layer depends downward only. Calls travel down; data flows back up.
- Best for. Server applications where the team and the codebase are both medium-sized and a single deployment is acceptable.
- Watch out for. Anaemic domain — when business rules drift up into the application layer and the domain becomes a bag of getters. Hexagonal is the corrective.
Microservices#
Each bounded context is its own deployable service with its own database; services communicate over the network.
- Shape. Many small processes. APIs (REST/gRPC/queues) between them. Independent deploy cadences. Independent on-call rotations.
- Best for. Large organisations where independent team velocity matters more than transactional simplicity. Different parts of the product have very different scaling profiles.
- Watch out for. Distributed monolith — services that must deploy in lockstep because they share a schema or a synchronous call graph. The cost of micro is real only when the benefit of independent change is real.
Event-driven#
Components publish events; other components subscribe. The publisher does not know its subscribers.
- Shape. A broker (Kafka, RabbitMQ, EventBridge) in the middle. Producers fire-and-forget. Consumers pull or are pushed to.
- Best for. Asynchronous integration across teams or services. Decoupling write-heavy and read-heavy paths (CQRS). Auditable workflows where the event log is the source of truth.
- Watch out for. Hidden coupling through event schemas — when adding a field is as breaking as a synchronous API change. Treat events as versioned contracts.
Hexagonal (Ports and Adapters) / Clean Architecture#
The domain core sits in the centre with no outward dependencies. The framework, the database, the queue, and the UI are all adapters plugged in through ports (interfaces owned by the domain).
- Shape. Domain defines interfaces (
UserRepository,EmailSender). Adapters implement them (PostgresUserRepository,SesEmailSender). The dependency arrow points inward. - Best for. Long-lived systems where the framework or database is expected to outlast its first choice and tests should run without infrastructure.
- Watch out for. Over-abstraction for short-lived projects. A throwaway CRUD app does not need a hex.
Trade-off summary table#
| Pattern | Strength | Weakness | Reach for it when |
|---|---|---|---|
| MVC | Familiar; small ceremony | Controllers gather fat over time | UI with non-trivial domain logic |
| MVVM | Declarative binding removes glue | ViewModel can sprawl | Modern data-bound UI frameworks |
| Layered | Easy to teach; one deployable | Anaemic domain risk; one failure unit | Mid-size server with one team |
| Microservices | Team independence; per-service scaling | Operational tax; distributed bugs | Many teams with diverging change rates |
| Event-driven | Decoupled producers; replayable history | Eventual consistency; schema drift | Async integration; audit-by-default |
| Hexagonal | Framework-agnostic core; test-friendly | Indirection cost; learning curve | Long-lived systems; testability first |
A second axis, the one interviewers usually push on, is synchronous vs asynchronous coupling:
Synchronous (MVC, Layered, RPC-style microservices). A request fans out via direct calls. Latency stacks. Failure cascades unless circuit breakers are added. Easy to reason about; hard to scale heterogeneously.
Asynchronous (Event-driven, queue-backed microservices). Producers fire events; consumers process at their own pace. Latency is hidden by the queue. Failure is contained per consumer. Hard to debug end-to-end; easy to scale per service.
Real systems mix them. The user-facing path is usually synchronous; the analytics, search-indexing, and notification paths are usually asynchronous.
When this is asked in interviews#
Three patterns of question carry architectural design:
- “Sketch the architecture for X.” The interviewer wants the boxes and arrows. Start with the user-facing entry point, draw the layers or services, then explicitly mark the seams (sync vs async, deploy boundaries). Resist diving into class-level patterns until the architecture is on the board.
- “Why this and not that?” The trade-off question. Be ready to name the cost of your choice, not just the benefit. “Microservices because team independence” is half an answer; “microservices because team independence and we are accepting the operational tax of three on-call rotations” is the full one.
- “Where would you put the database?” A proxy for testing whether you understand dependency direction. The right answer in hexagonal is “behind a port the domain owns”; in layered it is “the infrastructure layer at the bottom”; in microservices it is “private to each service, never shared across boundaries.”
A common mistake — confusing architectural style with deployment topology. MVC describes how the code is organised; it does not say whether the result is one process or twenty. A microservice can be internally MVC. An event-driven system can be a single monolith. Naming the two axes separately keeps the conversation honest.
Related concepts#
- OOAD and UML — Overview — class-level decomposition; architectural patterns sit one floor up.
- Dependency Inversion Principle (DIP) — the principle that hexagonal architecture turns into a structural rule.
- Observer Pattern — the class-level shape of event-driven thinking.
- Facade Pattern — the natural seam between layers in a layered architecture.
- Strategy Pattern — how a layered or hexagonal core swaps adapters at runtime.