MVC pattern
MVC (Model–View–Controller) splits an application into three parts with distinct responsibilities, so presentation, input handling, and business/data logic evolve independently. It’s the default mental model for server-rendered web apps and the ancestor of most layered UI architectures.
The three components
| Component | Responsibility | Should not |
|---|---|---|
| Model | Data + business rules; persistence, validation, domain logic | Know about HTTP, HTML, or the UI |
| View | Presentation — render the model into a response (HTML, JSON) | Contain business logic or mutate the model |
| Controller | Handle input, call the model, choose a view | Hold business rules or rendering details |
The point is separation of concerns: each part has one reason to change. Swap the view (HTML → JSON) without touching business rules; change a rule without touching templates.
The request flow
request → Controller → Model (read/update data, run rules)
↘ View (render model) → response
- The controller receives the request (a route handler).
- It asks the model for data or tells it to change state.
- It hands the result to a view, which renders the response.
The model never calls the view; the view never mutates the model. Dependencies point inward, toward the model.
Minimal example
# Model — data + rules, no HTTP/HTML
class Account:
def __init__(self, balance):
self.balance = balance
def withdraw(self, amount):
if amount > self.balance:
raise ValueError("insufficient funds") # business rule lives here
self.balance -= amount
# View — turns a model into a response shape
def account_view(account):
return {"balance": account.balance}
# Controller — input handling + orchestration
def withdraw_controller(account, request):
account.withdraw(request["amount"]) # delegate to the model
return account_view(account) # delegate to the view
The controller stays thin: parse input, call the model, return a view. The rule (“insufficient funds”) lives in the model — see Where business logic goes.
MVC in Python web frameworks
Frameworks rarely use the exact MVC names, but the roles map cleanly:
| Framework | Model | “View” (presentation) | “Controller” (input/orchestration) |
|---|---|---|---|
| Django (calls it MVT) | Models (ORM) | Template | View function/class — Django’s “view” is the controller |
| Flask | SQLAlchemy models | Jinja templates | Route functions (@app.route) |
| FastAPI (API, not HTML) | Pydantic + services/ORM | Response model / serializer | Path operation function |
| Rails / Spring MVC | Model | View | Controller |
Django’s naming is the classic interview trap: its View is the controller and its Template is the view — which is why Django calls the whole thing MVT.
For JSON APIs there’s no HTML “view,” so the view role collapses into serialization (Pydantic models, DRF serializers) — the thing that shapes the response.
Where business logic goes
“Fat model, thin controller” is the guideline: business rules belong in the model layer; controllers only coordinate.
- Anemic model (anti-pattern): models are bare data bags and all logic sits in controllers → fat controllers, duplicated rules, logic that can’t be tested without HTTP.
- As apps grow, logic that doesn’t fit one model moves to a service layer between controller and models. MVC says nothing about this — which is why teams reach for layered / clean architecture and the repository pattern.
MVC vs MVP vs MVVM
| Pattern | Who mediates | Typical use |
|---|---|---|
| MVC | Controller routes input; view reads the model | Server-rendered web |
| MVP | Presenter holds all UI logic; the view is passive | Older desktop / Android |
| MVVM | ViewModel exposes bindable state; the view binds to it | Data-binding UIs (WPF, Vue, modern frontends) |
All three separate presentation from logic; they differ in how the UI and the mediator communicate — direct calls (MVC/MVP) vs data binding (MVVM).
Pitfalls
- Fat controllers — handlers doing validation, business rules, and persistence. Push rules down to the model/service.
- Logic in views/templates — business branching in templates. Views should only present.
- Models that import HTTP — a model that knows about
request/responseis no longer reusable or testable in isolation. - Expecting MVC to dictate architecture — MVC is about UI separation, not dependency direction or where infrastructure lives. That’s the job of hexagonal / clean architecture.
Interview angle
- “Explain MVC.” Three roles — model (data + rules), view (presentation), controller (input + orchestration) — separated so each changes independently.
- “In Django, what’s the View?” The controller. Django renames things: View = controller, Template = view → “MVT.”
- “Where does business logic go?” The model (fat model, thin controller); a service layer once it outgrows a single model — never in controllers or templates.
- “MVC vs MVVM?” MVVM adds a ViewModel the view data-binds to; MVC’s controller actively pushes data to the view. MVVM suits data-binding UIs, MVC suits request/response web.