skip to content

What is the difference between organizing a codebase's packages by technical layer (e.g. separate `controller`, `service`, and `repository` packages) versus organizing them by feature (e.g. a package per business capability like `orders` or `users`)?

level: juniorimportance: must knowfreq 85%

answer

  1. horizontal vs vertical slicing
  2. screaming architecture
  3. locality of change
  4. cohesion inside, coupling across

basics

~20 s

Layer-based grouping sorts files by their technical role (all controllers together, all services together). Feature-based grouping sorts files by what business thing they do (everything about orders lives in one folder, everything about users in another).

solid answer

~40 s

Package-by-layer slices the codebase horizontally: a `controllers/`, `services/`, `repositories/` package each hold classes from every feature, so `OrderController`, `UserController`, and `ProductController` all sit side by side. Package-by-feature slices vertically: an `orders/` package holds `OrderController`, `OrderService`, and `OrderRepository` together, and `users/` holds the analogous trio. Layer-based structure mirrors the technical stack and is what most tutorials and generated scaffolds default to. Feature-based structure mirrors the business domain - opening the `orders` folder shows everything needed to understand or change ordering, from HTTP handling down to persistence. Neither is about different code, only about how the same classes are grouped on disk, which then shapes how easy it is to find related code, how visibility can be scoped, and how a change to one feature ripples through the tree.

go deeper

for a junior

Should correctly describe both layouts in concrete terms (what goes in which folder) and give a one-sentence reason each has fans - doesn't need to defend a strong opinion or cite Screaming Architecture by name.

for a middle

Should connect the layout choice to locality of change with a concrete example (e.g. adding a field touches N files across N packages) and recognize the terms 'vertical slice' / 'horizontal layer'.

for a senior

Should articulate the cohesion/coupling mechanics precisely, mention encapsulation implications (package-private visibility), and have an opinion backed by experience on a real codebase, not just theory.

for a principal

Should discuss this as one input into a broader modularization strategy (module boundaries, hybrid layouts, migration cost for legacy code) and know when the standard advice doesn't apply.

## The question every codebase has to answer Every non-trivial codebase eventually has to answer a basic question: when I create a new class, which folder does it go in? There are two dominant answers, and they cut the same set of classes along different axes. ## Package-by-layer Package-by-layer (sometimes called 'horizontal slicing') groups classes by their **technical role** in the application's architecture. - A typical Spring or Express-style project ends up with top-level packages like `controllers`, `services`, `repositories`, and `dtos`. - Every controller in the system — `OrderController`, `UserController`, `PaymentController` — lives in the same `controllers` package, regardless of which business feature it belongs to. - It mirrors the classic **three-tier** mental model (presentation, business logic, data access) that most backend frameworks and tutorials teach by default, and it is the layout code generators and boilerplate templates produce out of the box. ## Package-by-feature Package-by-feature (also called 'vertical slicing' or organizing by 'module') instead groups classes by the **business capability** they implement. - An `orders` package contains `OrderController`, `OrderService`, `OrderRepository`, and `OrderDto` — everything needed to understand or modify ordering, regardless of which architectural layer each class belongs to. - A sibling `users` package holds the equivalent set for user management. - The package boundary tracks 'what this code is for', not 'what kind of class this is'. ## What the layer layout gets right The problem package-by-layer solves, historically, is **discoverability by role**: if you know you're debugging a persistence issue, you know to look in `repositories`, full stop. It also happens to be the layout that matches how architecture diagrams are usually drawn (boxes for tiers, arrows for calls), so junior engineers coming from a course or tutorial recognize it instantly. That's a real, if modest, benefit, and it's why almost every 'hello world' web app tutorial ships with this layout. ## The problem it creates The problem it creates is **locality of change**. Real feature work almost never respects layer boundaries: adding a `discountCode` field to orders touches `OrderController`, `OrderService`, `OrderRepository`, and probably `OrderDto` and a validator — four or five files scattered across four or five top-level packages, interleaved alphabetically with completely unrelated classes from every other feature. Robert C. Martin named this problem in his 'Screaming Architecture' essay: when you open the top level of a package-by-layer project, the folder names scream 'Spring MVC application' or 'this uses a repository pattern' rather than screaming 'this is an e-commerce system' — the technical framework is more visible than the business the software actually does. Package-by-feature flips that: the top level reads `orders`, `catalog`, `shipping`, `payments`, which is a table of contents for the business domain, not the tech stack. ## Cohesion and coupling The coupling and cohesion argument is the mechanical reason this matters beyond aesthetics. 1. **Cohesion** is how strongly the things inside one module belong together; **coupling** is how much modules depend on each other. 2. Package-by-layer produces packages with **low cohesion** internally — `services/` bundles `OrderService` next to `UserService` next to `PaymentService`, which have nothing to do with each other except that they're all 'services' — while coupling is smeared across the whole tree, because `OrderController` in one package has to import `OrderService` from another and `OrderRepository` from a third, for every feature, every time. 3. Package-by-feature inverts this: each feature package is **highly cohesive** (everything inside it collaborates to do one job) and, ideally, features are only loosely coupled to each other, often only through explicit shared interfaces or a small `common`/`shared` package. ## The encapsulation the layer layout cannot offer That inversion also unlocks encapsulation that package-by-layer structurally cannot offer in languages with package-scoped visibility (Java, Kotlin's internal-per-module patterns, Go's unexported identifiers). Under package-by-feature, `OrderRepository` can be **package-private** — visible only inside `orders` — so nothing outside the feature can bypass `OrderService` and query the database directly. Under package-by-layer this is nearly impossible, because `OrderRepository` sits in a `repositories` package alongside every other feature's repository, and anything with access to that package can reach in. ## Where most codebases land In practice, most production codebases end up doing package-by-feature at the top level and package-by-layer one level down — `orders/controller`, `orders/service`, `orders/repository` — getting the locality-of-change and encapsulation benefits of feature slicing while still giving each feature package internal structure a newcomer to that one feature can navigate.

  • If package-by-feature is generally better for locality of change, why do so many tutorials and starter templates still default to package-by-layer?
    Package-by-layer maps directly onto the layered-architecture diagrams (MVC, three-tier) that are taught first, so it's pedagogically simple to explain and to scaffold mechanically regardless of what the app does. It also doesn't require the tutorial author to know the target domain, whereas feature packages need real business concepts (orders, users) to organize around, which a generic 'todo app' tutorial often doesn't have much of.
  • Does package-by-feature mean you throw away the controller/service/repository distinction entirely?
    No - most real feature-package layouts keep those roles as sub-packages or files inside each feature folder, e.g. `orders/OrderController.kt`, `orders/OrderService.kt`. The distinction that disappears is the layer being the top-level grouping; it becomes an internal detail of each feature instead of the organizing principle for the whole codebase.

Package-by-layer is like organizing a hospital by job title - all doctors in one wing, all nurses in another, all pharmacists in a third - so treating one patient means walking through every wing. Package-by-feature is like organizing by patient ward - the cardiology ward has its own doctors, nurses, and pharmacists together, so everything for one patient's care is in one place.

saying these in an interview costs you the question

  • Says package-by-feature and package-by-layer produce different code, not just different folder placement of the same classes
  • Can't explain what 'screaming architecture' means when asked directly
  • Thinks locality of change is only about fewer file opens, not about reduced merge-conflict/blast-radius risk
  • Claims package-by-layer has no legitimate use case at all
  • Doesn't mention that a real change (e.g. adding a field) touches multiple layer packages under layer-based organization

context