skip to content

Compare horizontal layering with vertical feature slicing as ways of partitioning a system. What does each optimize for, and how do you choose?

level: seniorimportance: must knowfreq 61%

answer

  1. rows = technical role, columns = capability
  2. layers isolate tech volatility; slices isolate feature change
  3. feature change = thin vertical thread through layers
  4. slice first, layer inside the slice
  5. Conway's law + co-change history decide the axis

basics

~20 s

Layering groups code by technical role — UI, business logic, data access — so each technology is isolated. Vertical slicing groups code by feature, so everything for "checkout" sits together. Layers optimize for swapping technology; slices optimize for changing one feature.

solid answer

~60 s

Horizontal layering cuts by *technical role*: presentation, application, domain, persistence. It isolates technical volatility (swap the ORM, add a second UI channel), gives one obvious place for each kind of code, and is easy to enforce with dependency rules. Its cost is that a typical feature change is a thin slice through every layer — high touch count, cross-team coordination, and layers that become dumping grounds. Vertical slicing cuts by *capability* (checkout, pricing, onboarding), keeping a feature's handler, rules, and data access together. It optimizes for the dominant real-world change unit — a feature — enabling team ownership, per-slice technology choices, and small blast radius. Its cost is duplicated technical plumbing, weaker global consistency, and a risk of hidden coupling through a shared database. In practice both are used at different scales: slice first (modules per capability), layer inside each slice. Choose by asking which axis your changes actually arrive on — mine the commit history — and remember Conway's law: the partition that fights your team structure loses.

code

text · 13 lines
text
package-by-layer (horizontal)      package-by-feature (vertical)
  controllers/                       checkout/
    CheckoutController                 CheckoutEndpoint
    PricingController                  CheckoutRules
  services/                            CheckoutStore
    CheckoutService                  pricing/
    PricingService                     PricingEndpoint
  repositories/                        PriceRules
    CheckoutRepository                 PriceStore
    PricingRepository

"add a field to checkout" touches      "add a field to checkout"
3 packages, 3 owners                   touches 1 package, 1 owner

go deeper

for a junior

Explain the two shapes with a concrete folder example and note that a feature change touches many layers but one slice.

for a middle

Add the trade-offs on both sides — duplicated plumbing and consistency drift for slices, god-layers and high touch count for layers — and mention combining them.

for a senior

Argue from evidence: co-change analysis, rate of change, team ownership. Present slice-first-layer-inside as the default and name the shared-database trap.

for a principal

Tie the partition to organizational design (Conway's law, stream-aligned teams), the migration path (strangler-fig, module extraction, enforcement mechanics), and the explicit statement of which change axis you are deoptimizing and how you'll absorb that cost.

## The two axes Imagine a system as a grid: columns = business capabilities, rows = technical roles. ### Horizontal layering (cut along rows) Classic layers: - **Presentation/UI** — rendering and input. - **Application/orchestration** — use-case coordination, transactions. - **Domain/business logic** — rules and invariants. - **Infrastructure/persistence** — database, message brokers, external APIs. Rule: dependencies point one direction (usually downward, or inward in Clean/Hexagonal variants where infrastructure depends on the domain via interfaces). **Optimizes for:** isolating *technical* volatility. Replacing Postgres, adding a mobile client, or swapping a web framework touches one layer. Also easy onboarding ("where does a repository go?") and mechanical enforcement (dependency rules, ArchUnit-style tests, build-module boundaries). **Costs:** - A feature is a **vertical thread through all layers**, so "add a field" edits 5–8 files across 4 layers. This is the layered architecture's signature complaint. - Layers grow into **god-modules**: one `services` package holding 200 unrelated classes has no cohesion left. - **Anemic domain**: pushing all behavior into a service layer leaves domain objects as data bags. - **Sinkhole anti-pattern**: many requests pass through layers doing nothing but delegating. - Ownership is unclear — every team edits every layer, causing merge and release contention. ### Vertical feature slicing (cut along columns) Group by capability: `checkout/`, `pricing/`, `onboarding/`, each containing its own entry point, rules, and data access. Related ideas: **modular monolith** modules, **bounded contexts** (DDD), **vertical slice architecture** (Jimmy Bogard), **microservices** at the deployment scale, feature-based frontend folders, and **package-by-feature** vs. package-by-layer. **Optimizes for:** the *actual unit of change and ownership* — features and business capabilities. Benefits: - Small blast radius: one feature's change stays in one directory. - Clear team ownership (aligns with Conway's law and Team Topologies' stream-aligned teams). - Slice-local decisions: a read-heavy reporting slice can use raw SQL while a rule-heavy slice uses a rich domain model — the "one size fits all" tax of layering disappears. - Deletability: retiring a feature is deleting a folder, not archaeology across four layers. **Costs:** - **Duplicated plumbing** and drifting conventions unless shared kernels/platform libraries exist. - **Weaker global consistency**: cross-cutting policy changes (new audit requirement) must be applied per slice. - **Hidden coupling via a shared database**: slices that all read the same tables are only cosmetically separated — the schema becomes an uncontrolled integration point. - **Cross-slice features**: a change spanning three capabilities is now a three-module change, sometimes cross-team. - Harder to enforce mechanically than layer rules unless you add module boundaries and dependency checks. ## They are not exclusive — pick a primary axis per scale The mainstream synthesis: **slice first, then layer inside the slice**. Top-level packaging by capability; inside each capability, a small internal layering (api / domain / infrastructure). This gives feature-local change *and* technical isolation. Hexagonal/ports-and-adapters composes with this: each slice exposes ports; adapters live at its edge. What you should *not* do is duplicate the same axis at every level (a `services` layer inside every feature inside a `services` layer) — that's the indirection tax without the benefit. ## How to choose — evidence, not taste 1. **Mine change history.** Compute which files co-change. If commits cluster by feature, slice. If they cluster by technology ("all repositories changed when we upgraded the ORM"), layers earn their keep — but note that technology changes are rare and feature changes are constant, which is why slicing usually wins at the top level. 2. **Rate of change / volatility.** Separate what changes at different rates and for different reasons. Business rules change weekly; the persistence technology changes once a decade. 3. **Team topology.** Conway's law: your architecture will mirror your communication structure. If teams are feature/stream-aligned, layer-major architecture forces every team into every layer and creates coordination cost. If you have a specialist DBA/UI team, layer boundaries match reality. 4. **Consistency requirements.** Strong cross-feature invariants and shared transactions argue for keeping things in one module (or at least one database with a shared domain), tempering aggressive slicing. 5. **Team size / codebase size.** Small codebase and one team: layering is simpler and adequate. Many teams: slice, or contention dominates. ## Common traps - **Slicing by noun, not by capability.** `user/`, `order/`, `product/` looks vertical but often reproduces a data model, not a change axis; "checkout" spans all three nouns. - **"Microservices = vertical slicing."** Microservices are a *deployment* choice that also imposes network, consistency, and operational costs. You can get the modularity benefit inside one process (modular monolith) first, then extract only where independent scaling/deployment is genuinely needed. - **Slices sharing one mutable schema.** Without data ownership per slice, you have distributed layers, not slices. - **Assuming layers guarantee testability.** Testability comes from having business rules free of I/O — achievable in either shape.

  • If you slice vertically, how do you stop the shared database from re-coupling the slices?
    Give each slice ownership of its own tables/schema and forbid cross-slice table access — reads go through the owning slice's API or a published read model/event. Enforce it mechanically (separate schemas plus per-slice DB users/permissions, or module dependency tests), because convention alone erodes under deadline pressure.
  • Which changes still hurt in a vertically sliced system?
    Cross-cutting policy changes (new audit field everywhere, framework upgrade, auth model change) — they now touch every slice instead of one layer. Mitigate with a thin shared platform/kernel library owned by a platform team, plus automated migration tooling; accept that this axis is the one you deliberately deoptimized.
  • How would you migrate a large layered codebase toward vertical slices without a rewrite?
    Incrementally: pick one high-churn capability, create a module for it, move its code from each layer into that module behind a published interface, add a dependency rule preventing others from reaching inside, and repeat. Keep the old layers as the residual "not yet sliced" area. This is a strangler-fig approach and keeps the system releasable throughout.

A department store can be organized by material (all fabric on one floor, all metal on another) or by shopper intent (a complete camping section). Reorganizing for a new supplier is easy in the first; serving a camper who needs a tent, stove, and boots is easy in the second — and shoppers arrive far more often than suppliers change.

context