Episode 3 — NodeJS MongoDB Backend Architecture / 3.3 — Backend Architectures
3.3 — Interview Questions: Backend Architectures
Common interview questions about software architecture, MVC, separation of concerns, service layers, and distributed system patterns.
< Exercise Questions | Quick Revision >
How to use this material (instructions)
- Read lessons first --
README.md, then3.3.athrough3.3.d. - Answer aloud before reading the model answer -- this simulates interview pressure.
- Pair with
3.3-Exercise-Questions.mdfor hands-on practice. - Quick review --
3.3-Quick-Revision.mdfor last-minute revision.
Beginner Level
Q1: What is MVC and what does each letter stand for?
Why interviewers ask: Tests fundamental vocabulary -- every backend developer must know this cold.
Model answer:
MVC stands for Model-View-Controller. It is an architectural pattern that separates an application into three interconnected components. The Model handles data logic, database interactions, and business rules. The View handles the presentation layer -- in a REST API this is the JSON response; in a traditional web app it is the HTML template. The Controller receives incoming requests, extracts input, calls the Model or Service layer for processing, and sends the response back through the View. The core benefit is separation of concerns: each component has one responsibility and can be modified, tested, and maintained independently.
Q2: What is separation of concerns and why does it matter?
Why interviewers ask: This principle underpins every architecture question -- interviewers want to know you understand the "why," not just the "what."
Model answer:
Separation of concerns means that each module, layer, or component in your application should handle one category of responsibility and delegate everything else. In an Express backend, this translates to: routes define URL patterns, controllers handle HTTP request/response logic, services contain business logic, and models manage data access. It matters for three practical reasons: (1) testability -- you can unit-test a service function without spinning up an HTTP server, (2) maintainability -- changing a business rule in the service layer does not require touching the controller or model, and (3) team collaboration -- different developers can work on different layers simultaneously without merge conflicts.
Q3: What is the role of the Model in MVC? What should it NOT do?
Why interviewers ask: Probes whether you understand layer boundaries or whether you dump everything into one place.
Model answer:
The Model is responsible for data structure (schema definition), data validation (required fields, types, constraints), business rules tied to data (password hashing before save, computing derived fields), and database CRUD operations. The Model should not access req or res objects, should not send HTTP responses, and should not know anything about routing or the transport protocol. This keeps the Model reusable -- the same User model works whether the request comes from a REST API, a CLI script, or a cron job.
// GOOD -- Model handles data logic only
userSchema.pre('save', async function (next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 12);
next();
});
// BAD -- Model handles HTTP (leaking concerns)
userSchema.methods.sendResponse = function (res) {
res.status(200).json(this); // Model should not know about res
};
Q4: What is the role of the Controller? What is a "fat controller" and why is it bad?
Why interviewers ask: Fat controllers are the most common anti-pattern in junior codebases -- interviewers want to know if you can spot and fix it.
Model answer:
The Controller is the traffic cop of MVC. It receives the HTTP request, extracts input from req.params, req.query, and req.body, calls the appropriate service or model method, and sends the response with the correct status code. A controller should be thin -- typically 5-10 lines per handler.
A fat controller is one that contains business logic, database queries, validation, email sending, and response formatting all in one function. It is bad because: (1) it is untestable -- you need a full HTTP request to test any logic, (2) it is not reusable -- the same business logic cannot be called from another route or a background job, and (3) it violates the single responsibility principle.
// FAT controller (bad)
exports.createOrder = async (req, res) => {
// validation, price calculation, inventory check,
// database insert, email notification, response -- all here
};
// THIN controller (good)
exports.createOrder = async (req, res, next) => {
try {
const order = await orderService.createOrder(req.body, req.user.id);
res.status(201).json({ status: 'success', data: { order } });
} catch (error) {
next(error);
}
};
Intermediate Level
Q5: What is the Service layer and why add it between Controller and Model?
Why interviewers ask: Tests understanding of real-world architecture -- most production apps have a service layer, and interviewers want to know if you have worked with one.
Model answer:
The Service layer sits between the Controller and the Model. It contains business logic that does not belong in the Model (because it spans multiple models or involves external services) and does not belong in the Controller (because the Controller should only handle HTTP). Examples: creating a user involves hashing the password, checking for duplicates, creating the record, and sending a welcome email -- the service orchestrates all of this.
Benefits: (1) reusability -- the same userService.register() can be called from a REST endpoint, a GraphQL resolver, or a CLI script, (2) testability -- service functions accept plain objects and return plain objects, making them easy to unit-test without mocking HTTP, and (3) single responsibility -- the controller handles HTTP, the service handles business logic, the model handles data.
// services/userService.js
exports.register = async (userData) => {
const existing = await User.findOne({ email: userData.email });
if (existing) throw new AppError('Email already taken', 400);
const user = await User.create(userData);
await emailService.sendWelcome(user.email, user.name);
return user;
};
Q6: Explain the request flow through a typical MVC + Service layer application.
Why interviewers ask: Reveals whether you understand the full picture or just individual pieces.
Model answer:
The flow for POST /api/users looks like this:
- Client sends HTTP request
- Express middleware (body parser, auth, logging) processes the request
- Router matches the URL pattern and HTTP method to a route handler
- Controller extracts input from
req.body, callsuserService.create(data) - Service applies business logic (validation, password hashing), calls
User.create(data) - Model validates the schema, runs pre-save hooks, writes to the database, returns the document
- Service receives the document, performs any post-creation tasks (send email), returns result to controller
- Controller formats the response and sends it with
res.status(201).json(data) - Client receives the JSON response
Client -> Middleware -> Router -> Controller -> Service -> Model -> DB
<- <- <- <- <- <- <-
Each layer only communicates with its immediate neighbors. The Controller never talks directly to the database. The Model never sends HTTP responses.
Q7: What is the difference between a monolith and microservices?
Why interviewers ask: Architecture choice is a key design decision -- interviewers want to know you can weigh trade-offs, not just recite definitions.
Model answer:
A monolith is a single deployable unit where all features (users, orders, payments, notifications) live in one codebase and share one database. A microservices architecture splits each feature into an independent service with its own codebase, database, and deployment pipeline, communicating over the network via HTTP or message queues.
| Aspect | Monolith | Microservices |
|---|---|---|
| Deployment | One unit, deploy everything | Independent per service |
| Database | Shared | Each service owns its data |
| Scaling | Scale the whole app | Scale individual services |
| Complexity | Simple to start | High operational overhead |
| Team size | Best for small teams | Best for large, autonomous teams |
| Debugging | Single process, easy tracing | Distributed tracing required |
The key insight: start with a monolith (or modular monolith) and extract services only when you have a clear scaling or organizational need. Premature microservices add distributed-system complexity without proportional benefit.
Q8: What is a "distributed monolith" and why is it considered worse than both alternatives?
Why interviewers ask: Tests depth of understanding -- shows you have seen real architecture failures, not just textbook patterns.
Model answer:
A distributed monolith is a system that has been split into multiple services but still behaves like a monolith: services are tightly coupled, share the same database, must be deployed together, and a change in one service requires coordinated changes in others. You get the complexity of microservices (network latency, distributed failures, operational overhead) with none of the benefits (independent deployment, independent scaling, team autonomy).
It is worse than a monolith because you added network calls, serialization overhead, and partial-failure scenarios without gaining anything. It is worse than proper microservices because you do not have independent deployability.
Common causes: (1) splitting by technical layer (API service, database service) instead of by business domain, (2) services sharing a database, (3) synchronous HTTP calls chaining through every service for a single request.
Advanced Level
Q9: Compare SOA (Service-Oriented Architecture) and Microservices. How did microservices evolve from SOA?
Why interviewers ask: Tests historical and conceptual depth -- differentiates senior candidates who understand architectural evolution.
Model answer:
SOA and microservices both organize applications as a collection of services, but they differ in scope and implementation:
| Aspect | SOA | Microservices |
|---|---|---|
| Communication | Enterprise Service Bus (ESB) | Lightweight protocols (HTTP/REST, gRPC, message queues) |
| Service size | Large, coarse-grained (entire business domain) | Small, fine-grained (single bounded context) |
| Data sharing | Often shared databases or schemas | Each service owns its database |
| Governance | Centralized (ESB orchestration) | Decentralized (smart endpoints, dumb pipes) |
| Technology | Often uniform tech stack | Polyglot (each service picks its own stack) |
| Typical era | 2000s enterprise Java/XML | 2010s+ cloud-native |
Microservices evolved from SOA by removing the heavy ESB middleware, shrinking service boundaries, and embracing the Unix philosophy: each service does one thing well. The cultural shift was equally important -- microservices came with DevOps, CI/CD, containers, and the idea that the team that builds a service also operates it.
Q10: When should you move from a monolith to microservices? What signals indicate it is time?
Why interviewers ask: Tests practical judgment -- many candidates know the theory but cannot articulate when to actually make the transition.
Model answer:
Signals that it might be time:
- Team bottlenecks -- multiple teams are stepping on each other's code, merge conflicts are constant, and deployment coordination is a full-time job.
- Scaling mismatch -- one part of the app (e.g., search) needs 10x the resources of everything else, but you are forced to scale the entire monolith.
- Deployment risk -- a small change to the notification module requires redeploying the entire application, risking the payment flow.
- Technology constraints -- a module would benefit from a different language, framework, or database, but the monolith forces a single stack.
Signals that it is not time: small team (fewer than 15-20 developers), no scaling bottleneck, limited DevOps maturity, or the product is still in early discovery and domain boundaries are unclear.
The recommended path is: Monolith -> Modular monolith -> Extract the first service -> Gradually extract more. The modular monolith step is critical because it forces you to define clean boundaries before adding network complexity.
Q11: How does event-driven architecture work, and when is it better than request-response?
Why interviewers ask: Tests understanding of asynchronous patterns that are essential for scalable systems.
Model answer:
In event-driven architecture, components communicate by producing and consuming events rather than making direct calls. When something happens (user signs up, order placed, payment received), the originating service publishes an event to a message broker (RabbitMQ, Kafka, Redis Streams). Other services subscribe to events they care about and react independently.
User Service Email Service
| |
+-- publishes "user.created" --> Message Broker
|
+----+----+
| |
Email Svc Analytics Svc
(send welcome) (log signup)
When it is better than request-response:
- Decoupling: the User Service does not need to know about Email or Analytics services.
- Resilience: if the Email Service is down, events queue up and are processed when it recovers.
- Scalability: consumers can be scaled independently based on their throughput needs.
When request-response is better: when you need a synchronous answer (e.g., "does this user exist?"), or for simple CRUD operations where the added complexity of a message broker is not justified.
Q12: Describe the Serverless architecture pattern. What are its real trade-offs?
Why interviewers ask: Serverless is increasingly common -- interviewers want to know if you understand when it fits and when it does not.
Model answer:
Serverless (Functions-as-a-Service) lets you deploy individual functions (AWS Lambda, Google Cloud Functions, Azure Functions) that are triggered by events (HTTP requests, file uploads, database changes, scheduled timers). The cloud provider handles provisioning, scaling, and infrastructure. You pay only for execution time.
Advantages:
- Zero server management and automatic scaling
- Pay-per-invocation (ideal for spiky or low-traffic workloads)
- Fast deployment of individual functions
Trade-offs:
- Cold starts: inactive functions take 100ms-2s to initialize, which hurts latency-sensitive APIs
- Execution limits: typically 15 minutes max per invocation, limited memory
- Vendor lock-in: function triggers, environment variables, and deployment configs are provider-specific
- Debugging difficulty: distributed traces across many functions are harder than tracing through a monolith
- Cost at scale: for always-on, high-throughput workloads, a dedicated server or container is cheaper than millions of invocations
Best fits: image processing triggers, scheduled jobs, webhooks, infrequent APIs, prototypes. Poor fits: always-on APIs with steady high traffic, long-running processes, applications requiring persistent connections (WebSockets).
Quick-Fire Table
| # | Question | One-Line Answer |
|---|---|---|
| 1 | What does MVC stand for? | Model-View-Controller |
| 2 | What does the Model handle? | Data logic, schema, validation, database CRUD |
| 3 | What does the Controller handle? | HTTP request/response, delegates to service/model |
| 4 | What is a fat controller? | Controller with business logic, DB queries, and side effects mixed in |
| 5 | What does the Service layer do? | Business logic that spans multiple models or external services |
| 6 | Why separate concerns? | Testability, maintainability, team collaboration |
| 7 | Monolith = | Single deployable unit, shared database |
| 8 | Microservices = | Independent services, own databases, network communication |
| 9 | What is a distributed monolith? | Microservices that are still tightly coupled -- worst of both worlds |
| 10 | SOA vs Microservices | SOA = ESB + coarse services; Microservices = lightweight + fine-grained |
| 11 | Event-driven advantage | Decoupled producers and consumers, resilient to failures |
| 12 | Serverless best fit | Spiky/low-traffic, event-triggered functions |
<- Back to 3.3 -- Backend Architectures (README)