Designing is fundamentally about taking things apart... in such a way that they can be put back together. So separating things into things that can be composed that's what design is.
— Rich Hickey Design. Composition and Performance
When we write complex applications, we need to perform various operations, sometimes completely different from each other:
- update data on the server;
- show a popup after user click;
- validate data from a form;
- load additional resources, images, scripts;
- call third-party API and process the response.
It is considered good practice to divide differing code into modules that are responsible for their specific tasks. How to divide the code into modules, by what criteria and principles—these questions are what the MVC pattern strives to answer.
Briefly
MVC (short for Model—View—Controller) is an architectural pattern that divides modules into three groups:
- model (model),
- view (view),
- controller (controller).
The model contains the application data that the user interacts with. For example, a list of their orders in an online store.
The view displays this data in a way that is understandable to the user. For example, on a rendered web page or in a mobile app.
Controllers accept user commands and transform data according to these commands. For example, if the user clicks the "Delete Order" button, the controller marks this order as deleted in the model.
Architecture Components
In the MVC architecture, the user interacts only with the view—most often this is the UI.
The user sends commands to the program. The controller receives these commands and transforms the data in the model. The model updates and notifies the view that it needs to redraw the interface to display the changes in data.

Let's say we want to write a flashlight application. It will have two states: on and off. The states will toggle with the "On/Off" button. It will also have buttons for turning on daylight and nightlight, which will change the bulb's color to blue and yellow respectively.

Let's try to write it using the MVC pattern.
Model
The model contains data for the application. This is the most independent component of the architecture; what the model shows will dictate what the view displays and how the controller will work.
In the model, we will keep the state of the flashlight. By default, we will turn it off:
const flashLightModel = { isOn: false, color: "blue",}
const flashLightModel = { isOn: false, color: "blue", }
When the user turns on the flashlight, the is
field will be set to true
, and this will be the controller's responsibility. The color
field contains what color the flashlight will glow.
Controller
The controller accepts commands from the user and transforms data in the model according to these commands. In our application, the controller will contain a function for toggling the flashlight state:
const flashLightController = { toggle() { flashLightModel.isOn = !flashLightModel.isOn },}
const flashLightController = { toggle() { flashLightModel.isOn = !flashLightModel.isOn }, }
The controller can also accept and process data from the view. For example, we could switch the color with special buttons, so the controller checks which button was pressed to turn on the appropriate color:
const flashLightController = { // Other code selectColor(e) { const buttonName = e.target.name const buttonColors = { daylight: "blue", nightlight: "yellow", } const preferredColor = buttonColors[buttonName] flashLightModel.color = preferredColor },}
const flashLightController = { // Other code selectColor(e) { const buttonName = e.target.name const buttonColors = { daylight: "blue", nightlight: "yellow", } const preferredColor = buttonColors[buttonName] flashLightModel.color = preferredColor }, }
In the example above, the controller checks which color button was pressed: daylight or nightlight. Depending on the button pressed, it selects the appropriate color.
View
The view presents the model data to the user in a convenient and understandable way. In our case, the view will be the flashlight itself, which can glow two colors, and buttons for turning it on and switching colors.
<div class="flashlight"></div><button type="button" name="power">Turn On</button><button type="button" name="daylight">Daylight</button><button type="button" name="nightlight">Nightlight</button>
<div class="flashlight"></div> <button type="button" name="power">Turn On</button> <button type="button" name="daylight">Daylight</button> <button type="button" name="nightlight">Nightlight</button>
In addition to the markup, the code that manages the flashlight's display can also be attributed to the view:
const flashLightView = { redraw() { const { isOn, color } = flashLightModel const flash = document.querySelector(".flashlight") flash.classList.add(`has-color-${color}`) if (isOn) { flash.classList.add("is-on") } },}flashLightView.redraw()
const flashLightView = { redraw() { const { isOn, color } = flashLightModel const flash = document.querySelector(".flashlight") flash.classList.add(`has-color-${color}`) if (isOn) { flash.classList.add("is-on") } }, } flashLightView.redraw()
And also—the code for handling events that the view will give to the controller:
const flashLightView = { // Other code initEvents() { const powerButton = document.querySelector(`[name="power"]`) powerButton.addEventListener("click", () => flashLightController.toggle()) // Code for events of other buttons },}
const flashLightView = { // Other code initEvents() { const powerButton = document.querySelector(`[name="power"]`) powerButton.addEventListener("click", () => flashLightController.toggle()) // Code for events of other buttons }, }
Component Interaction
When using the MVC architecture, we define how components will communicate with each other—that is, define data flows.
Data Flow
In classic MVC, the standard is when data:
- is sent from the user to the view;
- from the view to the controller;
- the controller updates the model;
- the model notifies the view that something has changed.
Sometimes it is permissible for components to communicate directly with other components outside of this scheme, but we still recommend not straying from the canon.
View or Controller?
In MVC, there is often a question of where to place certain code: in the view or in the controller. In our above example, there are even places like this.
const flashLightView = { // ... initEvents() { const powerButton = document.querySelector(`[name="power"]`) powerButton.addEventListener("click", () => flashLightController.toggle()) },}
const flashLightView = { // ... initEvents() { const powerButton = document.querySelector(`[name="power"]`) powerButton.addEventListener("click", () => flashLightController.toggle()) }, }
The method init
in the view can also belong to the controller if we decide that centralized event processing for specific elements is the controller’s task.
Similarly with the select
method in the controller:
const flashLightController = { // Other code selectColor(e) { const buttonName = e.target.name const buttonColors = { daylight: "blue", nightlight: "yellow", } const preferredColor = buttonColors[buttonName] flashLightModel.color = preferredColor },}
const flashLightController = { // Other code selectColor(e) { const buttonName = e.target.name const buttonColors = { daylight: "blue", nightlight: "yellow", } const preferredColor = buttonColors[buttonName] flashLightModel.color = preferredColor }, }
If we decide that event handling is the responsibility of the view, then we can move the color selection function to the view and leave only the method for changing the color in the controller:
const flashLightController = { updateColor(color) { flashLightModel.color = color },}
const flashLightController = { updateColor(color) { flashLightModel.color = color }, }
Since MVC allows the user to interact directly with the controller, there are no specific rules here, only recommendations:
- It is advisable to use consistent rules for the controller, model, and view throughout the entire project.
- If rules are intentionally violated, this should be documented along with the reasoning.
- Rules should derive from the areas of responsibility of each component, which should be determined in advance.
How Much Should the Controller Know?
Another similar question that arises when working with MVC is what the controller's area of responsibility is, and where it ends.
With a thin controller, knowledge about how to transform data resides in the model. This is not always useful, as it can clutter the model code, but sometimes it is justified. For example, if we do not want to "spread" this knowledge across multiple components.
This problem is called the thin and thick controller problem. Different teams solve it in their own way based on agreements and the benefits and drawbacks of each option for their project.
Similar Patterns
Besides MVC, you may have heard of other variations of this abbreviation. Each variation is a pattern that slightly differs from MVC in terms of component specificity or responsibility.
Model—View—Viewmodel
In MVVM (short for Model—View—Viewmodel), the controller is replaced with a Viewmodel. This is an "overlay" on top of the view that binds data and the view so that developers no longer need to write the logic for updating the UI and handling user commands themselves.

For binding to work, a Binder is needed—a framework, library, or even a language that automatically maps changes from the model to the UI.
Model-View-Presenter
In MVP (short for Model-View-Presenter), the presenter takes the place of the controller.
The main difference from MVC is how the components are arranged and, consequently, how data is passed. While in MVC data was passed in a circle, in MVP the components are arranged in a line. The model and view are on the ends, and the presenter is in between.

The presenter takes on all the logic for data processing, updating the view, and handling user commands.
In this case, the view is passive: it does nothing but display data as instructed by the presenter. If in MVC the view could handle output formatting, in MVP the presenter will be responsible for that as well.
The advantage of this approach is that there are no questions about what code belongs where. The downside is that the presenter quickly becomes large and complex. It is often necessary to break it into smaller modules, possibly adding additional "layers."