skip to content

Package by Feature vs by Layer

Group by technical layer and every feature is spread across four packages; group by feature and a change stays in one place. You will learn how each layout affects locality of change and coupling, and why the package tree should say what the application does.

part ofSoftware design & architectureoverview, primer and where to startread it →
on this pageshow

questions

5

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

open as a page

Under package-by-layer organization (separate `controllers/`, `services/`, `repositories/` packages each containing classes for every feature), how many files and packages typically need to change to add a new `discountCode` field to an `Order`, and how does that compare under package-by-feature organization (a single `orders/` package holding `OrderController`, `OrderService`, and `OrderRepository`)?

level: middleimportance: must knowfreq 80%

basics

~20 s

With layer folders, adding one field touches files spread across several separate top-level folders (controller, service, repo). With feature folders, all those files sit together in one orders folder, so the change stays in one place.

open as a page

How does package-by-feature versus package-by-layer affect coupling and cohesion at the package level, and are there situations where you'd deliberately choose package-by-layer despite its locality-of-change downsides?

level: seniorimportance: must knowfreq 70%

basics

~20 s

Feature packages keep related code together (high cohesion) and keep unrelated features apart (low coupling between packages). Layer packages do the opposite - unrelated features get lumped together, while each feature's own code is spread thin. Layer-based still makes sense for very small apps or when the tech layers themselves are the reusable, shared thing, like a generic data-access library.

open as a page

What does 'screaming architecture' (a term popularized by Robert C. Martin) mean, and how does choosing package-by-feature over package-by-layer support it?

level: middleimportance: should knowfreq 55%

basics

~20 s

It means a codebase's top-level folder names should tell you what business the software does, not what framework or technical pattern it uses. Feature folders like orders and shipping do that; layer folders like controllers and services don't.

open as a page

You've inherited a large, multi-year-old Spring backend organized strictly package-by-layer (`controllers/`, `services/`, `repositories/`, roughly 40 classes in each), with several teams actively shipping features into it. How would you approach migrating it toward package-by-feature, and what structure would you land on internally within each feature package?

level: principalimportance: should knowfreq 40%

basics

~20 s

Don't do a giant rewrite. Move one feature at a time into its own folder while the app keeps running, starting with the feature that changes most often. Inside each feature folder, still keep some internal grouping (like controller/service/repo) so it's not one giant flat pile of files.

open as a page