MVC pattern

4 min read source

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
  1. The controller receives the request (a route handler).
  2. It asks the model for data or tells it to change state.
  3. 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/response is 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.