Comparisons

Pairs that get conflated in real conversations and in real pull requests — coupling and cohesion, abstraction and indirection, refactoring and rewriting, debt and mess. Neither column wins; what decides is the requirement. Each record leads with the confusion, because the confusion is the reason the record exists.

Simple vs Easy

What people get wrong about this pair

The pair is repeated as a slogan with an obvious moral — simple good, easy bad — and that reading loses the whole point, which is that they are *independent axes*. Something can be simple and unfamiliar: a data-oriented core with explicit effects at the edges is objectively fewer braided concerns and is harder for a team that has only written service classes. Something can be easy and complex: an ORM that makes the first query one line while braiding together persistence, identity, caching, lazy loading and transaction lifetime behind an attribute. The mistake teams make in one direction is choosing the familiar thing repeatedly until nothing can be reasoned about locally; the mistake in the other direction is imposing a genuinely simpler design on a team that will not be able to maintain it, which is a real cost that simplicity advocates tend to wave away. The useful move is to name which axis you are arguing on. "This is hard for us" is a legitimate objection about the team and can be answered with time, examples and pairing. "This braids four concerns" is an objection about the artefact, and no amount of familiarity fixes it.

Simple — few interleaved concerns; one thing, not braided together
Use it when

When the code will be read, changed and debugged for years by people who were not there. Simplicity is what keeps the cost of the tenth change close to the cost of the first.

Easy — near at hand; familiar, quick to start with, low friction today
Use it when

When the horizon is short, the team is small and known, or the friction of the unfamiliar would genuinely cost more than the interleaving. Easy is a real value, not a vice.

DimensionSimple — few interleaved concerns; one thing, not braided togetherEasy — near at hand; familiar, quick to start with, low friction today
A property ofThe artefactThe relationship between the artefact and this team, today
Measured byHow many concerns are interleaved in one placeHow quickly someone gets started
Changes over timeOnly if you change the designYes — familiarity is learnable
Pays offOn the tenth change, and on the incident at 3amOn the first day, and on the deadline this week
Typical seductionPurity for its own sake, imposed on people who did not choose itA one-line API that hides four decisions you will later need
Right objection to raise"This braids persistence into the rule""Nobody here has used this and we ship on Thursday"
How to answer itSeparate the concerns, or accept the cost explicitlyPairing, examples, time — or choose the familiar thing on purpose
Are they opposedNo — they are independent, and the best designs are bothNo — but when they conflict, say which axis you are arguing on

The same question, five structures

Layered, hexagonal, clean, vertical slice and modular monolith — compared without naming a winner, and with the block that says where the comparison stops being true.

CONTESTED

A tidy table implies an equivalence that does not exist. These are not five points on one axis: layered, hexagonal and clean are statements about dependency direction; vertical slice is a statement about directory grouping; modular monolith is a statement about deployment and module visibility. Most real systems combine several. The where this comparison misleads block on every row is the part worth reading, and it is the reason this page names no winner — none of these is mandatory, and a team that adopts one because a diagram was pretty has skipped the only question that decides it.

What it actually organises
Layered
Code by technical kind. Presentation sits above application, above domain, above data access, and each layer may call downward only.
Hexagonal
Code by whether it is inside or outside. The application defines ports it needs; adapters on the outside implement them, and the direction of the dependency is inward.
Clean
Concentric rings by rate of change and by policy versus mechanism, with a hard rule that source dependencies point inward toward entities and use cases.
Vertical slice
Code by capability. Each feature owns its whole path — entry point, rules, persistence, tests — and layering, if any, happens inside the slice.
Modular monolith
Code by module, in one deployable. Modules have explicit public contracts and private internals, enforced by the build rather than by convention.
Where this comparison misleads

These are not five points on one axis. Layered, hexagonal and clean are all statements about *dependency direction*; vertical slice is a statement about *directory grouping*; and modular monolith is a statement about *deployment and module visibility*. You can — and most real systems do — combine several of them: a modular monolith whose modules are vertical slices, each with a hexagonal boundary at its edges. Comparing them as alternatives is the single most common way this table is misread.

Complexity it adds
Layered
Low at first. The layer names are conventional, the rule is easy to state, and most frameworks come pre-shaped this way.
Hexagonal
Moderate: a port interface and an adapter for every outside thing the application touches, plus wiring that chooses which adapter is live.
Clean
Highest of the three dependency styles: entities, use cases, interface adapters and frameworks as distinct rings, with mapping between the types of adjacent rings.
Vertical slice
Low structurally, but it moves the difficulty into judgement — someone must decide what a feature is and where cross-cutting work lives.
Modular monolith
Moderate, and mostly in tooling: module boundaries have to be enforced somewhere, and the shared platform underneath needs an owner.
Where this comparison misleads

The word complexity is doing two jobs here. Layered and vertical slice are cheap to *set up* and can be expensive to *live in* once the codebase is large; clean and hexagonal are expensive up front and their cost is roughly flat afterwards. Any comparison made at week one inverts the ranking you would get at year three, and neither reading is dishonest — they are answering different questions.

Change locality — where one feature change lands
Layered
Across every layer: controller, service, repository, model, mapper. The feature exists in five directories and is owned by none of them.
Hexagonal
Inside the application core if the change is a rule; inside one adapter if it is about an outside system. Rule changes and integration changes are cleanly separated.
Clean
Similar to hexagonal, with an extra hop: a change to a data shape often means touching an entity, a use case and the mapping between rings.
Vertical slice
One directory, usually one team, frequently one pull request. This is the property the style exists to deliver.
Modular monolith
Inside one module, if the boundaries match the domain. When they do not, the change crosses module contracts and becomes a negotiation.
Where this comparison misleads

Locality is a property of whether the boundaries match the change history, not of the style name. A vertical slice cut along the wrong capability lines has terrible locality, and a layered codebase with only one real feature has perfect locality. The only honest way to compare these columns is to open the last thirty merged changes in your own repository and count the directories each one touched.

Testability
Layered
Depends entirely on how the layers are wired. If the service constructs its repository, the layering buys nothing; if dependencies are passed in, the domain layer tests cleanly.
Hexagonal
Strong by construction: the core has no outward dependency, so it is tested with in-memory adapters and no infrastructure at all.
Clean
Same strength as hexagonal for the inner rings, with more mapping code that itself needs tests and rarely gets them.
Vertical slice
Naturally suits testing the slice end to end at its entry point, which is closer to how the feature is actually used and further from a fast unit test.
Modular monolith
Module contract tests are the characteristic unit: exercise a module through its public surface with the others faked, and enforce that no test reaches into internals.
Where this comparison misleads

Every column here is a claim about *fast tests without infrastructure*, and any of the five achieves that as soon as dependencies are injected rather than constructed — which is a separate decision none of these styles owns. What differs is the default test boundary each one nudges you toward, and that matters more than the theoretical maximum: layered nudges toward class-level tests with mocks, vertical slice toward feature-level tests, and the difference shows up in how much your suite has to change during a refactor.

Domain complexity it repays at
Layered
Any, because it does not really engage with domain complexity. It organises files; the rules can be thin or thick and the layout is unchanged.
Hexagonal
Repays when the outside world is complex or volatile — several integrations, a provider you expect to replace, a domain that must be testable without any of them.
Clean
Repays when the domain rules are genuinely rich and long-lived enough that isolating them from delivery mechanisms pays for the mapping. Below that, it is ceremony.
Vertical slice
Repays when features are numerous and largely independent, regardless of whether the rules inside each one are deep.
Modular monolith
Repays when several teams share a codebase and need enforced independence without the operational cost of separate deployments.
Where this comparison misleads

The columns are answering to different pressures: hexagonal responds to *external* volatility, clean to *domain* richness, vertical slice to *feature count*, and modular monolith to *team count*. A system can score high on one pressure and low on the rest, which is why picking a style from a comparison table rather than from your own pressures is how teams end up with four rings around a CRUD application.

Team fit
Layered
Suits a single team, or one where specialists own tiers. It gives everyone the same obvious place to put a new file, which has real value with junior engineers.
Hexagonal
Suits a team that agrees about where the domain boundary is. The port definitions are a design conversation that has to actually happen.
Clean
Suits teams with the discipline to maintain the ring rule under deadline pressure, and enough people that the mapping cost is amortised.
Vertical slice
Suits several teams owning different capabilities, and makes ownership files almost write themselves.
Modular monolith
Suits multiple teams who need independence in the code but do not want, or cannot yet afford, independent deployment and operation.
Where this comparison misleads

Team fit is not a tiebreaker, it is often the deciding factor, and it is the one this table cannot capture. A structurally superior design that the team will not maintain under deadline degrades into the worst version of itself — half-applied clean architecture, with some code respecting the ring rule and some not, is harder to work in than consistent layering. The right question is which of these your team will still be following in eighteen months.

Onboarding cost
Layered
Lowest. Nearly every engineer has seen it, the names are industry-standard, and a new joiner can place a file correctly on day one.
Hexagonal
Moderate. The inward dependency rule is one idea, but the vocabulary — ports, adapters, primary and secondary — is unfamiliar and inconsistently used across the industry.
Clean
Highest. Four rings, mapping between them, and a rule that feels arbitrary until someone explains which change it makes cheap.
Vertical slice
Low for reading — the whole feature is in one place — and higher for writing, because a new joiner has to know which slice owns the thing they are adding.
Modular monolith
Low per module and moderate overall: you learn one module quickly, and the map of which modules exist and what they promise takes longer.
Where this comparison misleads

This row compares familiarity, not intrinsic difficulty, and familiarity is a property of the industry at a moment in time rather than of the design. Layered wins here largely because it is what most people have seen, which is an argument for it and also the reason it is over-applied. It is also worth separating cost-to-read from cost-to-contribute-correctly: vertical slice inverts on those two, and the table's single number hides it.

Ceremony per feature
Layered
A file per layer, plus a mapper, whether or not the feature needs them. A trivial read endpoint costs the same ceremony as a complex workflow.
Hexagonal
A port plus an adapter for each new outside dependency; features that touch nothing new add none.
Clean
The most: entity, use case, request and response models, and mapping in both directions across the ring boundaries.
Vertical slice
The least, by design. A simple feature is allowed to be a single small file, and a complex one grows its own internal structure.
Modular monolith
Little inside a module; real ceremony when a feature crosses a module contract, which is intentional friction.
Where this comparison misleads

Ceremony is only waste when the feature did not need it, and every column here is right for some features and wrong for others in the same codebase. That is the actual finding of this row: a uniform ceremony level applied to every feature guarantees you are overpaying on the simple ones or underpaying on the complex ones. Allowing different features to carry different amounts of structure is more valuable than choosing which column to standardise on.

Where it degrades
Layered
Into a codebase where every feature change is a shotgun change, and the service layer becomes the god object that everything passes through.
Hexagonal
Into ports with one adapter each, defined by the vendor API rather than by the domain, so the boundary adds a hop and protects nothing.
Clean
Into mapping code that outweighs the logic, and an inner ring so guarded that adding a field means editing four types and two mappers.
Vertical slice
Into a shared/ directory that grows back under a new name, or slices that each reimplement infrastructure slightly differently.
Modular monolith
Into modules that reach into each other's tables because the contract was inconvenient once and nothing enforced it.
Where this comparison misleads

Every one of these degradations is the style's own strength taken past the point where it repays — which is why none of them can be called wrong, and why §138 forbids teaching any as mandatory. What makes a codebase bad is not the column it started in but the absence of anyone asking whether the structure still matches the changes arriving. The right comparison to make is between your current structure and your last thirty changes, not between two names on a page.