Most codebases follow a familiar structure. Controllers in one folder. Services in another. Repositories somewhere else. It feels organized—until you need to understand how a single feature actually works.

Suddenly you're jumping between layers, tracing calls through abstractions, piecing together logic scattered across the project. The architecture that promised separation of concerns has delivered separation of understanding.

Vertical slice architecture offers a different organizing principle. Instead of grouping code by technical role, you group it by business capability. Everything needed for a feature lives together—handler, validation, data access, all in proximity. The result is code you can reason about by looking in one place.

Layer Problems: When Horizontal Structure Works Against You

Traditional layered architecture emerged from reasonable instincts. Separate your presentation from your business logic from your data access. The theory goes that you can swap out any layer without affecting the others.

In practice, this rarely happens. How often have you replaced your data access layer while keeping everything else unchanged? Meanwhile, every feature you build requires touching multiple layers. Add a new field to an entity and you're modifying the controller, the service interface, the service implementation, the repository interface, and the repository implementation.

The cognitive cost compounds quickly. To understand how user registration works, you need to mentally reassemble logic spread across five or six files in different folders. Code reviews become exercises in cross-referencing. Onboarding new developers means teaching them to navigate a scavenger hunt.

Worse, horizontal layers encourage the wrong kind of reuse. Developers create shared services used by multiple features, coupling unrelated parts of the system. A change to the email service for password reset might break invoice notifications. The layers promised isolation but delivered hidden dependencies.

Takeaway

Architecture should optimize for understanding. When related code lives in different places, every modification requires reassembling context that was never actually separated in the business domain.

Slice Structure: Keeping Features Self-Contained

A vertical slice contains everything needed to handle a single use case. Consider a user registration feature. In a sliced architecture, you might have a folder called RegisterUser containing the request model, the handler that orchestrates the logic, the validation rules, and the data access specific to this operation.

The handler isn't a thin controller calling a service. It is the feature. It validates input, applies business rules, persists data, and returns results. There's no artificial separation forcing you to create interfaces for implementation details you'll never swap.

This doesn't mean abandoning all abstraction. Cross-cutting concerns like authentication, logging, and database connections remain shared infrastructure. The difference is that feature-specific logic stays feature-specific. Your registration validation rules don't need to account for login scenarios.

The structure scales surprisingly well. When a codebase has two hundred features, you have two hundred focused folders rather than a handful of massive layers. Finding relevant code becomes trivial—look in the folder named after what you're working on. Deleting a feature means removing one directory, not hunting through multiple layers to excise scattered remnants.

Takeaway

Self-contained features reduce the blast radius of changes and make the codebase navigable by name. When you can delete a feature by removing a folder, your architecture is aligned with how you actually work.

Coupling Implications: Trading One Dependency Pattern for Another

Every architectural choice involves trade-offs. Vertical slices don't eliminate coupling—they relocate it. Instead of features coupling through shared layer abstractions, slices couple to shared infrastructure.

This trade favors independence over consistency. In layered architecture, if two features use the OrderService, changes to that service affect both features. In sliced architecture, each feature has its own logic, which might lead to duplication. You might have similar validation code in CreateOrder and UpdateOrder.

Some duplication is acceptable—even beneficial. The validation rules for creating an order aren't necessarily the same as updating one, even if they look similar today. Keeping them separate lets each evolve according to its own requirements without coordinating changes.

The infrastructure layer requires discipline. Database contexts, messaging clients, and external service adapters remain shared. The key is keeping this layer thin and stable. It provides capabilities, not business logic. When a slice needs to send email, it uses the email infrastructure. How it uses it—what templates, what timing, what conditions—lives in the slice.

Takeaway

Duplication between features is often cheaper than coupling through shared abstractions. Code that looks similar but changes for different reasons belongs in different places.

Vertical slice architecture isn't universally superior to layered architecture. Small applications with straightforward CRUD operations might find layers perfectly adequate. The patterns you choose should match the problems you actually face.

But as systems grow and features multiply, the organizational model matters more. Sliced architecture offers a structure where understanding, modifying, and deleting features becomes proportional to their complexity—not to the size of the overall codebase.

The question isn't which approach is objectively better. It's whether your current structure helps or hinders the work you do every day. If understanding a feature requires reassembling scattered code, consider what it would mean to keep that code together.