Skip to content
StackPractices
intermediate By Mathias Paulenko

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

VariantUse CaseTrade-off
Classic MVCDesktop apps (Smalltalk-style)Tight view-model coupling
MVPTestable UI layersPresenter becomes a “god class”
MVVMFrontend 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.