MVC Pattern
Separate application into Model, View, and Controller components. An architectural design pattern for organized, maintainable code.
Note: This guide follows English-language naming conventions and terminology standards common in international development teams. Examples use English identifiers and comments to maximize compatibility across codebases and tooling.
MVC Pattern
Overview
The Model-View-Controller (MVC) Pattern is an architectural design pattern that separates an application into three interconnected components: Model (data and business logic), View (presentation), and Controller (input handling and coordination).
It is the foundation of many web frameworks (Django, Ruby on Rails, ASP.NET MVC) and desktop application architectures.
When to Use
Use the MVC Pattern when:
- You want a clean separation between data, UI, and user interaction logic
- Multiple views need to display the same data (e.g., web and mobile)
- The UI changes frequently but the underlying data model stays stable
- You need to support different input mechanisms (web, CLI, API)
- Multiple developers work on different layers simultaneously
Solution
Python
class UserModel:
def __init__(self, name: str, email: str):
self.name = name
self.email = email
class UserView:
def display(self, user: UserModel):
print(f"User: {user.name} ({user.email})")
class UserController:
def __init__(self, model: UserModel, view: UserView):
self.model = model
self.view = view
def update_name(self, name: str):
self.model.name = name
self.view.display(self.model)
# Usage
controller = UserController(UserModel("Alice", "alice@example.com"), UserView())
controller.update_name("Alicia")
JavaScript
class UserModel {
constructor(name, email) {
this.name = name;
this.email = email;
}
}
class UserView {
display(user) {
console.log(`User: ${user.name} (${user.email})`);
}
}
class UserController {
constructor(model, view) {
this.model = model;
this.view = view;
}
updateName(name) {
this.model.name = name;
this.view.display(this.model);
}
}
// Usage
const controller = new UserController(
new UserModel("Alice", "alice@example.com"),
new UserView()
);
controller.updateName("Alicia");
Java
class UserModel {
String name;
String email;
UserModel(String name, String email) {
this.name = name;
this.email = email;
}
}
class UserView {
void display(UserModel user) {
System.out.println("User: " + user.name + " (" + user.email + ")");
}
}
class UserController {
private UserModel model;
private UserView view;
UserController(UserModel model, UserView view) {
this.model = model;
this.view = view;
}
void updateName(String name) {
model.name = name;
view.display(model);
}
}
// Usage
UserController controller = new UserController(
new UserModel("Alice", "alice@example.com"),
new UserView()
);
controller.updateName("Alicia");
Explanation
MVC divides responsibility into three layers:
- Model: Manages data and business rules. Notifies views when data changes.
- View: Renders the model’s data. In modern apps, this is often a template or component.
- Controller: Accepts user input, processes it, and updates the model or view accordingly.
In modern web frameworks, the Controller often maps HTTP routes to Model operations, while the View is rendered server-side or as a single-page application.
Variants
| Variant | Use Case | Trade-off |
|---|---|---|
| Classic MVC | Desktop apps (Smalltalk-style) | Tight view-model coupling |
| MVP | Testable UI layers | Presenter becomes a “god class” |
| MVVM | Frontend frameworks (Vue, Angular) | Two-way binding adds complexity |
What Works
- Keep Models ignorant of Views: Models should not know how they are displayed
- Make Views read-only from the Model: Views observe models, but do not modify them directly
- Keep Controllers thin: Business logic belongs in the Model, not the Controller
- Use the Observer Pattern for Model-to-View updates to reduce coupling
- Avoid direct Model access from Views: Always go through the Controller or a ViewModel
Common Mistakes
- Fat controllers: Putting business logic in controllers instead of models
- View-driven models: Changing the model structure to match a specific view’s needs
- Tight coupling: Views directly calling model methods instead of using events
- Over-engineering: Using full MVC for a simple script where separation adds no value
- Ignoring data flow: Allowing views to modify models directly, bypassing the controller
Frequently Asked Questions
Q: Is MVC still relevant with modern frontend frameworks? A: Yes, though often in evolved forms. React uses a unidirectional data flow that separates concerns similarly. Angular implements MVVM, which is a direct descendant.
Q: What is the difference between MVC and MVVM? A: MVVM replaces the Controller with a ViewModel that binds directly to the View via two-way data binding. It is more common in frontend frameworks.
Q: Can I use MVC in a serverless architecture? A: Yes, though the “Controller” may be an API Gateway or Lambda function, the “Model” is your data layer, and the “View” is the JSON response or rendered template.
Is this pattern suitable for small projects?
For small projects with few components, this pattern may add unnecessary complexity. Start simple and introduce the pattern when you feel the pain it solves.
How does this pattern compare to alternatives?
Each pattern makes different trade-offs. Review the variants table above and consider your specific constraints: team size, performance requirements, and future scaling plans.
Can I partially apply this pattern?
Yes. Many teams adopt patterns incrementally. Start with the core idea and add sophistication as needed. The pattern is a guide, not a strict blueprint.
Advanced Topics
Scenario: MVC for Web Dashboard
// MVC pattern: Model, View, Controller
// Model: data and business logic
class DashboardModel {
private metrics: Metric[] = [];
private subscribers: (() => void)[] = [];
addMetric(metric: Metric) {
this.metrics.push(metric);
this.notify();
}
getMetrics(): Metric[] { return [...this.metrics]; }
subscribe(cb: () => void) { this.subscribers.push(cb); }
private notify() { this.subscribers.forEach(cb => cb()); }
}
// View: renders the UI
class DashboardView {
constructor(private container: HTMLElement) {}
render(metrics: Metric[]) {
this.container.innerHTML = metrics.map(m =>
`<div class="metric-card">`,
`<h3>${m.name}</h3>`,
`<span class="value">${m.value}</span>`,
`</div>`
).join("");
}
}
// Controller: orchestrates Model and View
class DashboardController {
constructor(private model: DashboardModel, private view: DashboardView) {
this.model.subscribe(() => this.updateView());
}
async loadMetrics() {
const data = await fetch("/api/metrics").then(r => r.json());
data.forEach((m: Metric) => this.model.addMetric(m));
}
private updateView() {
this.view.render(this.model.getMetrics());
}
}
// Usage
const model = new DashboardModel();
const view = new DashboardView(document.getElementById("dashboard")!);
const controller = new DashboardController(model, view);
controller.loadMetrics();
// Comparison MVC vs MVP vs MVVM
| Pattern | View knows Model? | Coupling | Data binding |
|---------|-------------------|----------|--------------|
| MVC | Yes (directly) | High | Manual |
| MVP | No (via Presenter) | Medium | Manual |
| MVVM | No (via ViewModel) | Low | Automatic (Observable) |
Lessons:
- MVC separates data (Model), UI (View) and logic (Controller)
- The Controller is the only one that touches Model and View
- Model notifies changes through observers
- MVC is simple but View can couple to Model
- For modern apps, MVVM with data binding is preferable
### MVC vs MVVM: which do I use in frontend?
Use MVC for simple apps where the View is static and the Controller handles everything. Use MVVM (Model-View-ViewModel) for apps with data binding: the View updates automatically when the ViewModel changes. React uses a pattern similar to MVVM (state + JSX). Angular uses MVVM with Change Detection. Vue uses MVVM with reactivity. MVC is more common in backend (Spring MVC, Express MVC).
End of document. Review and update quarterly. Related Resources
Repository Pattern
Abstract data access logic behind a clean interface. An architectural design pattern for testable, maintainable data layers.
PatternObserver Pattern
Define a subscription mechanism to notify multiple objects about events. A behavioral design pattern for event-driven communication.
GuideREST API Design Guide
A thorough guide to designing clean, scalable, and maintainable REST APIs.
RecipeDependency Injection
Implement dependency injection to write testable, decoupled code across languages and frameworks.
DocADR Template
A reusable template for Architecture Decision Records that capture context, decision, and consequences.
GuideSoftware Architecture Guide
A guide to designing software architecture: monoliths vs microservices, layered architecture, data flow, and technology selection criteria.