Hello, I'm Maneshwar, and I'm building LiveReview — a blast-radius aware AI code review built for your business-critical systems. Star us to help devs discover the project, give it a try, and share your feedback to help improve the product.
Every mobile team eventually has the same argument.
Someone says the ViewController is 3,000 lines long and needs to be broken up.
Someone else says "just use MVVM," like that phrase alone fixes anything.
A third person mentions VIPER and the room goes quiet, because everyone has a VIPER war story.
I went down this rabbit hole recently and realized something that should have been obvious years ago: MVC, MVP, MVVM, MVVM-C and VIPER are not five different ideas.
They're one idea, wearing five different amounts of clothing.
The one shape underneath all of them
Strip away the acronyms and every pattern is arguing about the same three things.
A View, which is the face of the app. It renders pixels and captures your taps.
A Model, the brain, which owns the business logic and the data.
And a translator in between, whose entire job is making sure the View and the Model never talk to each other directly.
That translator is the part that keeps getting renamed. Controller. Presenter. View-Model. Everything else is a debate about how much power to give it, and who else gets to share the job.
Model-View-Controller is genuinely old, coming out of Smalltalk work at Xerox PARC in the late 1970s, which makes it nearly as old as the personal computer itself.
It was built to separate "what the data is" from "what's on screen," and for a single-window desktop app in 1978, that was already a big improvement over one giant blob of code that did everything.
To see where each pattern actually differs, let's run the exact same tiny feature through all five: a user tapping their profile picture to pick a new one.
MVC: everything reports to one controller
MVC connects the View and the Model through a Controller that does all the coordinating.
You tap the picture, the View tells the Controller, the Controller updates the Model, and once that's done the Controller turns around and tells the View to refresh.
It reads clean on a whiteboard, and honestly it's a fine choice for a small app. That's not a knock, it's the actual selling point.
class ProfileController {
onPhotoPicked(image) {
model.setAvatar(image) // update the model
view.render(model.avatar) // tell the view to refresh
}
}
The catch is that this Controller doesn't stay this small. Every new feature wants to talk to the Model somehow, and the Controller is the only door in the building.
Six months later it's not a controller, it's a lobby that every department in the company has to walk through.
MVP: the Presenter takes the homework off the View
MVP answers the bloated-controller problem by handing UI logic to a dedicated Presenter, and telling the View to do nothing but draw.
Tap the photo, the View notifies the Presenter, the Presenter updates the Model, formats whatever comes back into something display-ready, and pushes that formatted result straight into the View.
The View in MVP is deliberately dumb, which is a compliment here. A dumb View is a View you can unit test the Presenter against without spinning up any UI at all, since the Presenter only ever talks to a thin interface the View implements.
That testability is the entire reason teams reach for MVP. Not because it's fancier than MVC, but because "does the right thing happen" becomes a question you can answer without a simulator or a device.
MVVM: the View stops asking, and starts just knowing
MVVM swaps the Presenter's manual "update the view" call for two-way data binding between the View and a View-Model.
Pick a new photo, the View pushes that change into the View-Model through binding, the View-Model persists it to the Model, and when the Model's data changes, that change flows back to the bound View property automatically. No explicit refresh call, anywhere.
This is the pattern that plays nicest with reactive frameworks, because reactive frameworks are basically data binding with better marketing.
class ProfileViewModel {
@Observable var avatar: Image // View binds to this directly
func onPhotoPicked(image: Image) {
avatar = image // View updates instantly via binding
model.persist(image) // no explicit "refresh the view" call anywhere
}
}
Less boilerplate, definitely. The tradeoff shows up later, when a bug means tracing a chain of automatic notifications instead of reading a linear function call, which is a genuinely different debugging skill.
MVVM-C: somebody still has to decide where the app goes next
MVVM never says who's in charge of moving between screens, and by default that job quietly lands on the View-Model, which is exactly the thing MVVM was trying to keep simple.
MVVM-C fixes that by adding a Coordinator that sits above the binding triangle and owns navigation on its own. In our example, that's the move from the profile screen to the image picker and back, including whatever save logic happens in between.
Same View, View-Model, Model relationship as plain MVVM. The only new thing is that "where do we go next" has its own dedicated home, instead of leaking into whichever View-Model happened to trigger the transition.
VIPER: five jobs, five files, no exceptions
VIPER takes the "give everyone exactly one job" idea about as far as it can go.
View displays and forwards user actions. Interactor owns the business logic. Presenter prepares data for the View and talks to the Interactor. Entity is the raw data, the Model equivalent. Router owns navigation.
Tap the photo: View tells Presenter, Presenter tells Interactor to do the actual work, Interactor manages the Entity, Presenter formats the result back for the View, and if a screen change is needed, Router handles it.
protocol PhotoInteractorProtocol {
func updateAvatar(_ image: Image)
}
protocol PhotoPresenterProtocol {
func didPickPhoto(_ image: Image)
func didUpdateAvatar(_ image: Image)
}
protocol PhotoRouterProtocol {
func closePhotoPicker()
}
Notice there's no shared base class holding this together, just protocols. That's the whole VIPER pitch: maximum separation, maximum testability, and yes, maximum file count for updating a single profile picture.
It earns its keep on genuinely large apps with multiple teams working the same codebase. On a five-screen app it's mostly ceremony.
So which one do you actually pick
None of them are wrong, they're just answers to different problems.
MVC for a small app where shipping fast matters more than architecture purity.
MVP once you need the mediator to be testable in isolation but don't need data binding.
MVVM once your framework is reactive and binding removes real boilerplate.
MVVM-C once that same app also has navigation flows worth centralizing.
VIPER once the app and the team are both big enough that "everyone has one job" stops being overhead and starts being the thing that keeps ten engineers from stepping on each other.
flowchart TD
A[Starting a new client app] --> B{Small app, small team?}
B -->|Yes| MVC[MVC: simplicity wins, ship it]
B -->|No| C{Need the mediator unit-testable?}
C -->|Yes, no data binding needed| MVP[MVP: Presenter tests clean in isolation]
C -->|Need reactive data binding| D{Complex navigation flows too?}
D -->|No| MVVM[MVVM: binding kills boilerplate]
D -->|Yes| E{Large app, many teams?}
E -->|Not yet| MVVMC[MVVM-C: Coordinator owns navigation]
E -->|Yes| VIPER[VIPER: five single-job pieces, max modularity]
classDef decision fill:#f4d35e,stroke:#b8991f,color:#1a1a1a
classDef start fill:#e9ecef,stroke:#6c757d,color:#1a1a1a
classDef pick fill:#5ee6c8,stroke:#1f9c86,color:#1a1a1a
classDef heavy fill:#9d8cff,stroke:#5b4bcc,color:#1a1a1a
class A start
class B,C,D,E decision
class MVC,MVP,MVVM pick
class MVVMC,VIPER heavy
The pattern isn't the architecture. It's just how much ceremony your team is willing to pay for, in exchange for how much chaos it's trying to avoid.
Pick the smallest one that still lets you sleep at night, and upgrade only when the pain is real, not hypothetical.
Your team's attention is limited, and the deluge of AI-generated code is making it harder to keep production secure and reliable without slowing you down.
I'm building LiveReview, a blast-radius aware AI code review built for your business-critical systems.
Instead of presenting every diff with equal emphasis, LiveReview scores each change by blast radius — how far its impact reaches through your call graph — so you can focus attention where it actually matters.
Spend code review effort where business risk is highest — not spread evenly across every diff.
⭐ Star it on GitHub:
HexmosTech
/
LiveReview
Blast-Radius Aware AI Code Review for Business-Critical Systems
LiveReview: Blast-Radius Aware AI Code Review for Business-Critical Systems
LiveReview is an AI code reviewer that scores every hunk of a diff by blast radius: how far a change reaches through your call graph, how much persistent state it touches, and how well-tested it is. A 3-line change to a shared auth check can outrank a 300-line UI tweak. Your team's attention goes to the highest-risk code first, not spread evenly across every diff.
blast-radius-demo.mp4LiveReview's Blast Radius & Review Priority scoring, live in the diff viewer.
| The exact math, not a black box | Visualize blast radius at a glance | Every factor that feeds the score |
|---|---|---|
![]() |
![]() |
![]() |
How does Blast Radius scoring work? (a more technical explanation)
Here's the goal:
- A 3-line fix in a function used by 40 other files, that also writes to a database, should score high.
- A 300-line UI change in one file, fully covered by…
Click below to try LiveReview with your codebase:
This article was originally published by DEV Community and written by Athreya aka Maneshwar.
Read original article on DEV Community











