MVC pattern

Updated 4 min read source
On this page8
  1. The three components
  2. The request flow
  3. Minimal example
  4. MVC in Python web frameworks
  5. Where business logic goes
  6. MVC vs MVP vs MVVM
  7. Pitfalls
  8. Interview angle

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

text
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

python
# Model — data + rules, no HTTP/HTML
class Account:
    def __init__(self, balance):
        self.balance = balance
    def withdraw(self, amount):
        if amount > self.balance:
            # business rule lives here
            raise ValueError("insufficient funds")
        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 4

  • “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.