SlicesGENERALCONTESTEDILLUSTRATIVE

Vertical Slices

Not the database in week one, the backend in week two and the frontend in week three — but one product, from page to API to database and back, visible on day one. A slice is one narrow feature through every layer, and it is the unit in which a system actually becomes real.

The situation, the reflex, and why it stalls

Every lesson starts where being stuck starts: someone has a problem, and the first move that comes to mind feels like progress.

The question

What is the smallest unit of work that, when finished, means something works — and why is it a path through the layers rather than one layer?

The situation

You have the store's decomposition and a plan that says schema first, then endpoints, then pages. It is logical: each layer needs the one below. Three weeks in, the schema is elegant, the endpoints return fixtures, and there is still nothing anyone could click.

The reflex

Build the foundation first. Get the database right, because everything sits on it; then the API, because the UI needs it; then the UI. It is the order of dependencies and it means nothing is built on sand.

Why it stalls

The foundation is designed for needs that have not been discovered, because no feature has run through it. The products table gets columns for variants, images and SEO metadata on speculation, and the first real page needs a column nobody thought of.

What the reflex produces — and fails to produce
  • The foundation is designed for needs that have not been discovered, because no feature has run through it. The products table gets columns for variants, images and SEO metadata on speculation, and the first real page needs a column nobody thought of.
  • Nothing works until everything does. The first moment the layers meet is the last week, and every mismatch — the API returns cents, the page expects a formatted price; the schema has no cart, the page needs one — is found when there is least time.
  • Progress is reported as layers done. "Database: complete" is true and meaningless; the founder asks to see a product and there is nothing to show.
ProblemUnderstandRequirementsConstraintsUnknownsDecompositionSmallest StepModelExperimentObserveDebugLearnIterate

The move

Precisely enough to apply it to a problem you have never seen — not a slogan.

  • Pick one narrow feature and build it through every layer it needs, and no wider. "Show one product": a page, an endpoint, a query, a row. Finish that path before starting any other, and check it works by looking at the page.
  • Then pick the next feature and run it through the same layers, extending each only as far as the new feature requires. The schema grows one column at a time, each one with a page that needs it.
  • Let the slices come from the decomposition's leaves (Decomposition by Capability). A capability is already a vertical thing; a slice is a capability, or a piece of one, taken through the stack.
  • After each slice, say what it proved and what it did not. "Show one product" proves the layers connect and a product renders; it does not prove that the list scales, that the cart works, or that a missing product is handled. Those are the next slices, not defects.

One product, through every layer

The first slice, as a slice device. Notice how little each layer does, and notice the two required lines at the bottom: what passing proves, and what it does not. The second line is where the next slice comes from.

Show one product
A product page shows a real product's name and price
  1. PageFetches /products/{id} for a hard-coded id and renders name and price; nothing else.
  2. APIOne route, GET /products/{id}, returning name and price or a not-found.
  3. LogicNone yet — the handler calls the query and maps the row; a stub-shaped layer that will grow.
  4. DatabaseA products table with id, name, price; one row inserted by hand.
  5. ResponseThe row, as JSON, back through the API to the page, which repaints.
proves
The environment works; the four layers connect; a real row becomes a visible product; the not-found path exists.
does not prove
Listing, filtering, images, stock, anything about cart or checkout, what happens with many products, or that the page works for a product id typed by a user rather than hard-coded.

The same three weeks, two ways

The comparison is the reason the module exists: identical effort, one of which produced a store you can click and one of which produced three finished layers and no store.

Layer-first against slice-first
Week one database, week two backend, week three frontend
A complete schema designed from imagination; an endpoint set returning fixtures; pages rendering fixtures. First integration in the last week; every mismatch found then; nothing demonstrable until the end.
Product → list → cart → pending order → confirmation, each through every layer
A visible product on day one; each layer grown only as far as a feature needed; mismatches found the afternoon they were created; a demonstrable store at every point.

Integration is where the unknown lives — the API returns cents, the page expects a string — and a slice does integration every day in small pieces, while layer-first defers all of it to the moment with the least slack.

The order of slices

Slices are chosen from the decomposition's leaves, in an order that follows dependencies and then risk. The sequence below is one; the alternative names when you would choose another.

The store, as slices
  1. 1
    Show one product

    because The thinnest path through every layer; proves the environment before anything depends on it.

  2. 2
    List products; admin creates one

    because Extends the same layers one step; the catalog exists and products come from somewhere real.

  3. 3
    Add to cart, cart persists

    because First slice that writes on behalf of a customer; forces the guest-cart decision early.

  4. 4
    Checkout to a pending order with a fake payment

    because The core workflow becomes demonstrable end to end while the external edge is still stubbed.

  5. 5
    Real payment, then decline and no-answer

    because The riskiest slice, built once the path around it is proven (Decomposing Checkout).

a different valid order Risk-first: begin with "request a charge and receive a confirmation" as a slice with no store around it — a page with a button, an endpoint, a provider call, a stored outcome — when the provider integration is the unknown that could reshape everything. The product slice then comes second.

How to do it

Most important first.

  • Name the feature as something you could see: "a product page shows a real product". If the name is a layer — "the products endpoint" — it is not a slice.
  • List the layers the feature needs, from the user inward: page, request, handler, logic, storage, and back. Build exactly those, minimally (The Smallest Executable Thing).
  • Stub anything outside the path. The page can hard-code the product id; the handler can skip auth; both are written down as stubs.
  • Check by observation, then by test (A Slice Is Testable). The observation is the page; the test is the same path driven by code.
  • Write the doesNotProve list before choosing the next slice; the next slice is usually the most important item on it.

Worked on a concrete problem

The move has to produce something. This is what it produced.

  • The store's first slice: a product row in the database; an endpoint that returns one product by id; a page that fetches and shows its name and price. One column set, one route, one component. It took an afternoon and it can be shown to the founder. It proved: the three layers connect; the environment works; a product renders. It did not prove: the list works, images work, anything about cart or checkout.
  • The second slice: "the catalog page lists all products". The endpoint gains a list route; the query gains an ordering; the page gains a list. The schema did not change. The slice proved listing works; it did not prove filtering, paging or what happens with a thousand products.
  • The layer-first alternative, for contrast: the same three weeks would have produced a products table with a dozen columns, an endpoint set for products, carts and orders, and pages that render fixtures — and the first real product would still not be visible.

How you know it worked

What now exists that did not before, and what question you can now ask.

  • Something is clickable at the end of the first day, and it is backed by a real row.
  • Each layer grew only because a feature needed it; you can name the feature for every column and route.
  • For every slice there is a written list of what it did not prove, and the next slice was chosen from that list.

The questions you can now ask

The field this whole domain exists for. After this lesson, these are the questions to put to an unfamiliar problem.

Next questions
  • ?What is the narrowest feature I could see working, and which layers does it need?
  • ?What did the slice I just finished prove — and what did it leave unproven?
  • ?Which stub did I introduce to make this slice thin, and where is that written down?
  • ?What is the next slice, and is it the most important thing on the doesNotProve list?

What can go wrong

How the move itself fails
  • Slices too wide: "checkout" as one slice, through every layer, is the whole hard part at once. Slice narrower: create a pending order with a fake payment is a slice; checkout is a set of them.
  • Slices that never widen: a store built as thirty independent slices with no shared model, so the product row that the catalog slice created and the one the cart slice created are different tables.
  • Treating the first slice's shortcuts as decisions: the hard-coded product id and the skipped auth ship because nobody kept the stub list.
What the move costs
  • Each slice touches every layer, so a specialist works outside their specialty, and a shared layer is edited by every slice in turn — more merges, more small migrations.
  • The schema grows incrementally, and some incremental growth is a migration that a designed-once schema would have avoided.
  • A visible slice invites feedback on things that are stubs — "the price has no currency symbol" — and the feedback has to be triaged against the plan.
Misreads
  • "Never design the schema up front." Design the entities up front — from the requirements (Entities From Requirements) — and build the tables slice by slice. The model is thought about early; the columns arrive with the features that need them.
  • "Vertical slices mean no shared code." Slices share the model, the layers and the infrastructure; what they do not share is a schedule. The second slice reuses the first slice's endpoint machinery; it does not wait for an endpoint layer to be finished.
  • "A slice is a sprint." A slice is whatever narrow feature runs through the stack; some take an hour. Tying it to a calendar unit makes slices wide to fill the time.

Where this applies

Problem-solving advice is stated as universal far more often than it is. These labels say what each method is specific to — and where CONTESTED appears, the note gives the strongest form of the opposing view.

  • GENERALOne narrow feature through every layer it needs is the unit of progress for any system with layers — a web app, a CLI with a parser and an evaluator, a pipeline with ingest and transform and output.
  • CONTESTEDSome practitioners hold that for a system whose data model is well understood and whose consistency rules are strict — a ledger, a payments core — the schema and its constraints should be designed and built completely first, because incremental columns and migrations are where invariants get lost, and a slice that adds a column without its constraint has created a bug the next slice will meet. Their strongest point: a schema is the one layer whose mistakes are hardest to undo. The reply here is that the entities and invariants are designed first; the *tables* still arrive with features, each carrying its constraint.
  • ILLUSTRATIVEThe afternoon, the three weeks and the thousand products are invented to show the shape of the argument; no real schedule is being described.

Where the depth lives

This domain asks the question and hands the answer off by name.