Suggest an editImprove this articleRefine the answer for “MVC (model-view-controller) and MVP (model-view-presenter) design patterns”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**MVC vs MVP** are architectural patterns that differ in how the View connects to the Model. ```javascript // MVC: View can access Model directly class View { render() { console.log(this.model.getData()); } } // MVP: View only receives data from Presenter class ViewMVP { show(data) { console.log(data); } } class Presenter { update() { this.view.show(this.model.getData()); } } ``` **Key:** In MVP, the Presenter owns all Model-View communication. In MVC, the View can query the Model itself. | | MVC | MVP | |---|---|---| | View-Model | Direct | Via Presenter | | Testing | Harder | Easier | | Used in | Rails, ASP.NET MVC | Android, WinForms |Shown above the full answer for quick recall.Answer (EN)Image**MVC (Model-View-Controller)** and **MVP (Model-View-Presenter)** are architectural patterns that split application code into distinct roles: one manages data, one handles display, and one sits between them. The real difference is whether the View can talk to the Model directly. ## Theory ### TL;DR - MVC: the View can pull data from the Model itself; Controller handles user input - MVP: the View is passive and knows nothing about the Model; Presenter owns all logic and pushes data to View - Analogy: MVC is a restaurant where customers (View) can read the menu directly; MVP puts the waiter (Presenter) as the only link between customers and the kitchen - Decision rule: pick MVP when you need to unit test Views without a real UI (Android, desktop); MVC works for web frameworks that handle binding automatically - Android moved away from MVP toward MVVM with Jetpack Compose, but MVP questions still come up in interviews regularly ### Quick Example The core difference in one runnable snippet: ```javascript // MVC: View directly accesses Model class Model { getData() { return 'user data'; } } class View { constructor(model) { this.model = model; } // View holds Model reference render() { console.log(this.model.getData()); } // direct pull } const model = new Model(); new View(model).render(); // Output: user data // MVP: View knows nothing about Model, only talks to Presenter class Presenter { constructor(model, view) { this.model = model; this.view = view; } updateView() { this.view.show(this.model.getData()); } // Presenter pushes } class ViewMVP { show(data) { console.log(data); } // no model reference here } new Presenter(model, new ViewMVP()).updateView(); // Output: user data ``` Both produce the same output. The difference is who holds the connection. ### Key Difference In MVC, the View can observe or query the Model for real-time updates. That simplifies reactive UIs but ties the View to the Model's structure, which makes testing harder without a live Model. MVP breaks this link completely. The View exposes only methods like `show(data)` or `showError(msg)`, and the Presenter calls them. The View never touches the Model, which makes it mockable in tests: pass a fake View to the Presenter and verify what it receives. ### When to Use - Web apps with data-binding frameworks (React, Angular): MVC works well, the framework handles Controller-View sync automatically - Android apps (pre-Compose) that need View unit tests: MVP, mock the View interface without touching the Model - TDD projects with strict test isolation: MVP, Presenter centralizes all logic - Simple prototypes: MVC, fewer files, faster iteration - Heavy business logic that gets refactored often: MVP, Presenter owns everything in one place ### Comparison Table | Aspect | MVC | MVP | |---|---|---| | View-Model link | Direct (View can query Model) | None (Presenter mediates) | | Data flow | User → View → Controller → Model → View | User → View → Presenter → Model → Presenter → View | | Testing | Harder (View needs real Model) | Easier (mock View interface) | | Coupling | Medium (View knows Model structure) | Low (View is passive) | | Boilerplate | Less | More (Presenter per View) | | Frameworks | Ruby on Rails, ASP.NET MVC, Spring MVC | Android (pre-Compose), WinForms, GWT | | When to use | Reactive web UIs, backend apps | Test-heavy apps, Android login flows | ### How the Patterns Work in Frameworks In Ruby on Rails, the router dispatches HTTP requests to Controllers, which query Models via ActiveRecord and render Views (ERB templates). The View has direct access to data passed by the Controller, and in some cases can call Model methods directly. In Android MVP, the Activity implements a View interface. The Presenter holds a reference to that interface, not to the Activity itself. Dependency injection (Dagger, Hilt) wires them at runtime. This is what makes swapping a real Activity for a mock View in tests trivial. One thing I noticed in production codebases: the worst bugs in MVC projects are not really about architecture. They come from Controllers ballooning to 200-line god objects because developers put validation, DB queries, and email sending all in one route handler. MVP's Presenter discipline forces that separation. ### Common Mistakes **Mistake: calling Model directly from an MVP View** ```java // Wrong: View touching Model directly class LoginActivity implements LoginView { void onLogin() { if (model.authenticate(email)) showSuccess(); // coupled to Model } } ``` This defeats test isolation - the View can no longer be mocked cleanly. Fix: delegate everything to the Presenter. ```java // Correct: View only delegates class LoginActivity implements LoginView { void onLogin() { presenter.login(email, pass); } public void showSuccess() { /* navigate */ } public void showError(String msg) { /* show toast */ } } ``` **Mistake: business logic in MVC Controllers** ```javascript // Wrong: validation and DB query inside the Controller app.post('/order', (req, res) => { if (!req.body.item) return res.status(400).json({ error: 'missing item' }); const result = db.query('INSERT INTO orders ...'); // Model logic here res.json(result); }); ``` Push logic to the Model. Controllers should only route requests and return responses. **Mistake: ignoring View lifecycle events in MVP** If the user presses back or rotates the screen, the Presenter needs to know. Forgetting `presenter.onCancel()` or `presenter.onDestroy()` causes state drift and memory leaks, because the Presenter holds a reference to a View that no longer exists. **Mistake: duplicating Model queries across MVC Controllers** ```javascript // BAD: same query written twice app.get('/users', (req, res) => { const users = db.query('SELECT * FROM users WHERE active=1'); // inline res.json(users); }); app.get('/user/:id', (req, res) => { const user = db.query('SELECT * FROM users WHERE id=? AND active=1', [req.params.id]); // duplicate res.json(user); }); // FIXED: extract to UserModel class UserModel { async getActive() { return db.query('SELECT * FROM users WHERE active=1'); } async getById(id) { return db.query('SELECT * FROM users WHERE id=? AND active=1', [id]); } } ``` Duplicate inline queries are a common source of N+1 bugs in Express codebases. ### Real-world Usage - Ruby on Rails: full MVC, Models via ActiveRecord, Views via ERB templates - ASP.NET MVC: Controllers handle HTTP, Models via Entity Framework Core, Views via Razor - Spring MVC: Controllers annotated with `@Controller`, Views via Thymeleaf or JSP - Android (pre-Compose): MVP with Activity as View, Presenter survives config changes via retained fragments - GWT (Google Web Toolkit): MVP for Java-to-JS compilation, Presenter manages DOM events - WinForms: MVP variant for testable desktop UI ### Follow-up Questions **Q:** Draw the data flow for MVC vs MVP on a whiteboard. **A:** MVC: user triggers View → View notifies Controller → Controller updates Model → Model notifies View (or View polls). MVP: user triggers View → View calls Presenter method → Presenter updates Model → Presenter calls View method with new data. **Q:** How would you unit test an MVP Presenter? **A:** Mock the View interface with Mockito in Java: `LoginView mockView = mock(LoginView.class)`. Inject it into the Presenter, call `presenter.login("bad@email.com", "wrong")`, then verify `verify(mockView).showError("Invalid credentials")`. No Activity, no UI, no emulator needed. **Q:** What is the real downside of MVP's extra layer? **A:** Boilerplate. Every View needs a matching Presenter and usually an interface. In a large app that means hundreds of extra classes. For small features it feels like over-engineering. **Q:** Why did Android move away from MVP? **A:** Jetpack Compose changed how UI works - declarative state-driven rendering removes the need for a passive View. MVVM with LiveData or StateFlow fits better because ViewModel survives configuration changes natively, while Presenter lifecycle management was always manual and error-prone. **Q:** (Senior) How do you handle multiple Views sharing one Model in MVP? **A:** Create a separate Presenter per View, each owning its own View interface. Share the Model via dependency injection (single instance in the DI graph) or a pub-sub channel like RxJava subjects. Never share mutable state between Presenters directly - that reintroduces the coupling you were trying to avoid. ## Examples ### Android Login Flow (MVP) A real-world login screen following the MVP pattern used in Android before Compose: ```java // Model: pure business logic, no Android imports class UserModel { boolean authenticate(String email, String pass) { return email.equals("test@test.com") && pass.equals("pass"); } } // View interface: what the Presenter can call on the UI interface LoginView { void showSuccess(); void showError(String msg); } // Presenter: owns all logic, mediates between Model and View class LoginPresenter { private UserModel model; private LoginView view; LoginPresenter(UserModel m, LoginView v) { model = m; view = v; } void login(String email, String pass) { if (model.authenticate(email, pass)) view.showSuccess(); else view.showError("Invalid credentials"); } } // Activity implements View interface, delegates everything to Presenter public class LoginActivity implements LoginView { LoginPresenter presenter = new LoginPresenter(new UserModel(), this); public void onLoginClick(String email, String pass) { presenter.login(email, pass); // Activity knows nothing about Model } public void showSuccess() { /* navigate to dashboard */ } public void showError(String msg) { /* show toast with msg */ } } ``` Testing this is straightforward: mock `LoginView`, inject into `LoginPresenter`, call `presenter.login(...)`, verify which View method was called. No emulator, no Activity, fast test. ### Express.js MVC - Keeping Controllers Thin A common pattern in Express apps is writing Model logic directly inside route handlers. Here is the fixed version: ```javascript const express = require('express'); const app = express(); // Model layer: query definitions live here, not in controllers class UserModel { async getActiveUsers() { return db.query('SELECT * FROM users WHERE active=1'); } async getActiveUser(id) { return db.query('SELECT * FROM users WHERE id=? AND active=1', [id]); } } // Controllers stay thin: routing and response only app.get('/users', async (req, res) => { const model = new UserModel(); const users = await model.getActiveUsers(); res.json(users); }); app.get('/user/:id', async (req, res) => { const model = new UserModel(); const user = await model.getActiveUser(req.params.id); res.json(user); }); ``` The query definition lives in one place. Change the `WHERE active=1` condition once, and both endpoints get the fix automatically.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.