Back to blog
System Designadvanced

Architecture Patterns: Interview-Critical Guide

The four architectural styles every senior engineer must know β€” Monolith, Modular Monolith, SOA, Microservices, and Event-Driven. Architect-level trade-offs, decision frameworks, and the questions you will be asked in interviews.

SystemForgeApril 18, 202611 min read
ArchitectureMicroservicesMonolithSOAEvent-DrivenSystem DesignInterview
Share:𝕏

Architecture is the set of structural decisions that are expensive to reverse. An interviewer asking "what architecture would you choose?" is not looking for a buzzword β€” they are testing whether you can weigh trade-offs, name the actual costs, and match a style to a problem. This guide gives you the intellectual framework to do that.


The Five Styles β€” At a Glance

Coupling:   Tight ◄──────────────────────────────────► Loose
Complexity: Low   ◄──────────────────────────────────► High

          Monolith β†’ Modular Monolith β†’ SOA β†’ Microservices
                                              ↑
                                    Event-Driven (cross-cuts all)

No style is inherently better. Each exists because different contexts have different dominant constraints. The skill is knowing which constraint matters most for this system.


Style 1: The Monolith

All code deployed as a single unit. One codebase, one process, one database.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              Order Service               β”‚
β”‚                                          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚  Orders  β”‚ β”‚  Users   β”‚ β”‚  Billing β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚             Single Database              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         One deploy. One process.

Real strengths (not just "simple")

  • Transactional consistency is free. Multi-step operations are ACID transactions. No saga, no 2PC, no compensating transactions.
  • Local calls. No network latency, no serialization, no service discovery.
  • Single deployment. One pipeline, one artifact, one environment to test.
  • Debuggability. One stack trace spans the entire operation.

Real weaknesses

  • Deployment coupling. A CSS change requires a full redeploy of the billing engine.
  • Scaling unit is the whole thing. You cannot scale order processing without scaling user auth.
  • Team coupling. Multiple teams editing the same codebase at high velocity produces conflict.
  • Technology lock-in. The entire system is one language, one framework, one runtime.

When monolith is the right choice

  • Team < 8 engineers
  • Business domain not yet understood (premature decomposition into services is expensive)
  • Startup finding product-market fit
  • Internal tool, low traffic, single team ownership

Interview trap: interviewers sometimes frame "monolith" as inherently wrong. Push back. Amazon, Shopify, and Stack Overflow ran monoliths at serious scale. The monolith's problems are team and deployment problems β€” not performance problems.


Style 2: Modular Monolith

Single deployable unit, but internally organised into strongly bounded modules with enforced interfaces between them. No shared database tables across module boundaries.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   Single Process                       β”‚
β”‚                                                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”            β”‚
β”‚  β”‚  Orders Module  β”‚   β”‚  Billing Module β”‚            β”‚
β”‚  β”‚  ─────────────  β”‚   β”‚  ─────────────  β”‚            β”‚
β”‚  β”‚  IOrderService  │──►│  IBillingPort   β”‚            β”‚
β”‚  β”‚  (public API)   β”‚   β”‚  (public API)   β”‚            β”‚
β”‚  β”‚  ─────────────  β”‚   β”‚  ─────────────  β”‚            β”‚
β”‚  β”‚  internal impl  β”‚   β”‚  internal impl  β”‚            β”‚
β”‚  β”‚  Orders DB sch. β”‚   β”‚  Billing DB sch.β”‚            β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜            β”‚
β”‚                   Shared Database Instance             β”‚
β”‚            (separate schemas, no cross-joins)          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The module boundary is enforced by access modifiers and architecture tests (ArchUnitNET, NDepend). Module A cannot reference the internal classes of Module B β€” only the public interface.

Why this is underrated

Modular monolith gives you:

  • Module independence (can be extracted to a service later when the boundary is proven)
  • Team independence within a single deployment
  • Transactional consistency where needed, message-based decoupling within the monolith where not
  • None of the distributed systems complexity

A modular monolith with good module boundaries is extractable to microservices when the need arises. A tangled monolith is not.

Enforcing module boundaries in .NET

C#
// ArchUnitNET β€” fail the build if a module violates its boundary
[Fact]
public void OrdersModule_ShouldNotReference_BillingInternals()
{
    Architecture.RejectAny()
        .Types().That().ResideInNamespace("Orders")
        .Should().HaveAnyAttributes(typeof(InternalAttribute))
        .And().DependOnAnyTypesThat()
        .ResideInNamespace("Billing.Internal")
        .Check(architecture);
}

When modular monolith is the right choice

  • Team 5–20 engineers, domain reasonably understood
  • Microservices are wanted eventually but domain boundaries are not yet stable
  • Single cloud region, single organisation (no cross-org integration requirement)
  • Most SaaS products at Series A/B stage

Style 3: Service-Oriented Architecture (SOA)

SOA predates microservices and is still the dominant pattern in enterprise IT. The defining characteristic: large, coarse-grained services orchestrated through a central Enterprise Service Bus (ESB).

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   Enterprise Service Bus (ESB)                   β”‚
β”‚          (routing, transformation, orchestration, security)      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”‚              β”‚               β”‚               β”‚
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚ CRM     β”‚   β”‚ ERP      β”‚   β”‚ Billing  β”‚   β”‚ Inventoryβ”‚
   β”‚ Service β”‚   β”‚ Service  β”‚   β”‚ Service  β”‚   β”‚ Service  β”‚
   β”‚ (SAP)   β”‚   β”‚(Dynamics)β”‚   β”‚(custom)  β”‚   β”‚(legacy)  β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

SOA characteristics

  • Services are enterprise systems, not code modules β€” SAP, Dynamics, mainframes, custom apps
  • Communication through the ESB (MuleSoft, IBM Integration Bus, BizTalk)
  • ESB handles protocol translation (SOAPβ†’REST, XMLβ†’JSON), routing, transformation, and orchestration
  • Services expose WSDL/SOAP contracts (historically); modern SOA uses REST or messaging
  • Shared canonical data model across the enterprise

What SOA solves

SOA emerged to solve enterprise integration β€” the problem of connecting heterogeneous, vendor-owned, legacy systems that cannot be rewritten. When your "microservices" would be SAP and Oracle, not Node.js services, SOA is the right frame.

SOA vs Microservices β€” the real distinction

| | SOA | Microservices | |--|-----|---------------| | Service size | Coarse-grained (entire business system) | Fine-grained (single capability) | | Communication | ESB (centralised) | Direct or via dumb broker (decentralised) | | Governance | Enterprise IT, central standards | Team-owned, autonomous | | Data | Shared canonical model | Each service owns its data | | Technology | Vendor platforms (BizTalk, MuleSoft) | Any language/framework per service | | Target | Integrating existing enterprise systems | Building new distributed systems |

Interview insight: SOA is not "failed microservices." It is the right architecture when your integration targets are large vendor systems, not services you build and own. Azure Integration Services (Service Bus + API Management + Logic Apps + Event Grid) is a modern cloud-native SOA platform.


Style 4: Microservices

Independently deployable services, each owning a single business capability and its data.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         API Gateway                           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
      β”‚             β”‚              β”‚               β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Orders  β”‚  β”‚ Products β”‚  β”‚  Users   β”‚  β”‚Payments  β”‚
β”‚  :8080   β”‚  β”‚  :8081   β”‚  β”‚  :8082   β”‚  β”‚  :8083   β”‚
β”‚  SQL DB  β”‚  β”‚  MongoDB β”‚  β”‚  PG DB   β”‚  β”‚  SQL DB  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

What you actually get

  • Independent deployment β€” the team owning Payments ships 10 times/day without coordinating with Orders
  • Independent scaling β€” scale the checkout service 10Γ— without touching auth
  • Technology autonomy β€” ML-heavy service uses Python; transaction-heavy service uses .NET
  • Fault isolation β€” Product catalogue outage does not take down order history

What you actually pay

This is what distinguishes architects from juniors. The costs are real:

Distributed transactions. You cannot have an ACID transaction spanning OrderService and InventoryService. You need sagas, the outbox pattern, eventual consistency β€” with all the edge cases that entails.

Network is the new stack. Every service call can fail, time out, or return stale data. You need retry, circuit breaker, timeout, bulkhead β€” for every call.

Distributed tracing. A single user action fans out across 8 services. Without tracing, debugging is archaeology.

Testing surface explodes. Unit tests per service + integration tests per service + contract tests between services + end-to-end tests across the mesh.

Operational complexity. Service discovery, health checks, secrets management, certificate rotation, zero-downtime deployment β€” per service.

Service boundary heuristics

The hardest part of microservices is drawing the right service boundaries. Wrong boundaries produce "distributed monolith" β€” all the complexity of microservices, none of the independence.

Domain-Driven Design bounded contexts are the correct tool:

  • Each service maps to one bounded context
  • Identify by: data that must be strongly consistent, a team that owns a business capability, a part of the domain with its own language (ubiquitous language)
  • Red flag: if two "services" need synchronous calls to complete most operations, they are one service split badly

Wrong cut:

UserAuthService ←→ UserProfileService  (always called together)
β†’ These are one bounded context split badly

Right cut:

OrderService β†’ publishes events
InventoryService β†’ reacts to events asynchronously
β†’ Independent lifecycles, asynchronous coupling

When microservices are the right choice

  • Multiple teams (8+ engineers) owning different business capabilities independently
  • Parts of the system have genuinely different scaling requirements
  • Technology diversity is a real requirement (ML team, Java legacy, new .NET services)
  • Domain boundaries are stable and understood β€” do not decompose a domain you don't understand yet

Style 5: Event-Driven Architecture

EDA is a communication style, not a deployment style. It can be applied to a monolith (internal domain events), a modular monolith (module-to-module via in-process bus), or microservices (service-to-service via Kafka/Service Bus).

Synchronous (Request/Response):
  OrderService ──HTTP──► InventoryService
  (waits for response, temporal coupling)

Event-Driven (Async):
  OrderService ──publishes──► [Broker] ──delivers──► InventoryService
  (fire and forget, temporal decoupling)

The core trade-off

| | Synchronous | Event-Driven | |--|------------|--------------| | Consistency | Strong (immediate) | Eventual | | Coupling | Temporal (caller waits) | Decoupled (async) | | Resilience | Caller fails if callee is down | Broker buffers, callee can be down | | Observability | Easy (one trace) | Hard (correlation across async hops) | | Complexity | Low | High (idempotency, ordering, schema evolution) |

When EDA is the right communication style

Use events when:

  • The calling service does not need an immediate result
  • Multiple downstream services care about the same fact (fan-out)
  • The downstream service may be temporarily unavailable
  • You want to decouple deployment lifecycles

Keep synchronous when:

  • You need the result immediately (read operations, user-facing queries)
  • The downstream service is part of the same transaction
  • The operation must not proceed without confirmation

Common interview question: "When would you choose synchronous over async?" The answer is: when you need the response to proceed, or when eventual consistency is not acceptable for the business requirement (e.g., payment authorisation must be confirmed before showing success to the user).


The Decision Framework

When asked "what architecture would you choose?" β€” work through this sequence:

1. How well is the domain understood?

  • Poorly understood β†’ Monolith or Modular Monolith (extract when boundaries stabilise)
  • Well understood, stable boundaries β†’ Microservices viable

2. What is the team structure?

  • 1–5 engineers, single team β†’ Monolith
  • 5–20 engineers, want module ownership β†’ Modular Monolith
  • Multiple teams, true autonomy needed β†’ Microservices

3. What are you integrating with?

  • Greenfield, you own everything β†’ Microservices or Modular Monolith
  • Enterprise systems (SAP, ERP, legacy) β†’ SOA / Azure Integration Services

4. What are the scaling requirements?

  • Uniform load β†’ Monolith (horizontal scale is still possible)
  • Wildly different scaling per capability β†’ Microservices

5. What is the consistency requirement?

  • Strong consistency critical everywhere β†’ Prefer monolith or modular monolith
  • Eventual consistency acceptable β†’ Event-driven microservices viable

Interview Questions You Will Be Asked

"What is the difference between SOA and microservices?" SOA integrates large vendor systems through a centralised ESB with a shared canonical data model. Microservices build new capabilities as fine-grained, independently deployable services with decentralised governance and per-service data ownership. SOA is an integration pattern; microservices is a build pattern.

"Why not use microservices from the start?" You don't understand your domain boundaries yet. Wrong boundaries produce distributed monolith β€” synchronous calls between services for every operation, shared databases "just for this one query," cascading failures across the mesh. Start monolith, extract services when boundaries prove stable and the team size warrants it.

"What is a distributed monolith?" Microservices in topology, monolith in practice. Services that cannot deploy independently (coupled releases), share databases, or require synchronous orchestration for every user operation. All the cost of distributed systems, none of the benefits.

"How do you handle transactions in microservices?" You cannot have ACID across services. Options: Saga pattern (local transactions + compensating actions), Outbox pattern (reliable event publishing with the DB transaction), or accepting eventual consistency. The answer depends on the business requirement β€” not everything needs strong consistency.

"When would you keep something as a monolith?" When the team is small, domain boundaries are unclear, or the operational cost of microservices (distributed tracing, service mesh, contract testing, independent deployments) exceeds the benefit. Monoliths can scale to significant load with horizontal replication. Netflix started as a monolith.


Related: Event-Driven Architecture Deep Dive
Related: Distributed Systems Patterns
Related: Azure Cloud Integration

Enjoyed this article?

Explore the System Design learning path for more.

Found this helpful?

Share:𝕏

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.