Slicing a Feature
A slice is a thin path through everything that delivers something observable. A layer is a horizontal band that delivers nothing, and calling it a slice is how all the risk ends up in the last week.
The requirement, the obvious build, and why it breaks
Every lesson starts where the work starts: someone asked for something, and the first implementation that comes to mind survives until the requirement changes.
How do I break a three-week feature into increments that are each safe to release and useful on their own?
Pause is three weeks of work. The team wants it broken up so that something is in production every week, and so that a week-two cancellation still leaves value behind.
Split it by layer: schema first, then the domain model, then the service, then the API, then the UI. Each piece is a clean unit of work, each has an obvious owner, and it is how the work naturally divides on a whiteboard.
Nothing is releasable until the last piece lands, so four weeks of work carry the risk of one deploy — and the deploy is the one at the end, made under the most schedule pressure (Change Size: Why Small Changes Are Safer, and When They Are Not).
- Nothing is releasable until the last piece lands, so four weeks of work carry the risk of one deploy — and the deploy is the one at the end, made under the most schedule pressure (Change Size: Why Small Changes Are Safer, and When They Are Not).
- Nothing is learnable either. The first real feedback arrives after the last layer, which is exactly when changing the schema is most expensive.
- The estimate for the last layer is wrong and there is nowhere to absorb it, because the previous three delivered no value that could be traded against scope.
- The layers are built for an imagined full feature, so the domain model gets fields the final UI never uses and the API gets endpoints nobody calls (Speculative Generality).
- It looks like progress the entire time. Three of four PRs merged, and the actual delivered value is zero — which is why this pattern survives so well (Speculative Generality).
What limits the solution, and what must never stop being true
This domain leads with these two. A design that ignores its constraints is not a design, and an invariant nobody named is one nothing is protecting.
- Weekly release train. Anything unfinished on a Thursday ships in whatever state it is in, behind whatever guard exists.
- The subscriptions table is live and shared with three other features, so schema changes must be independently deployable (Expand and Contract).
- Two engineers who would rather not work in the same files all week.
- Support needs to answer "can I pause a customer" with a yes or a no, not "partly" — so a half-built slice has to be invisible rather than half-visible.
- Every slice leaves the system releasable. Not "releasable if we finish the next one" — releasable as it stands, with a decided answer for every user who hits it.
- Every slice is independently revertible. If slice three is bad on Friday, reverting it does not undo slices one and two (Rollback: Only Useful If It Is Actually Safe).
- No slice leaves a state in the database that no code can interpret.
Who owns what, and where the seams fall
Responsibilities decide boundaries; boundaries decide what an interface has to say.
- Each slice owns one end-to-end behaviour, including its own migration, its own tests and its own flag.
- The first slice owns proving the shape: it is the one that finds out whether the state model survives contact with the billing job.
- The flag owns the visibility question, so that "shipped" and "released" become two different events with two different risks (Feature Flags and What They Cost).
- Nobody owns a layer. A layer is a consequence of slices, not a unit of work.
- A slice boundary falls where a user-visible behaviour begins and ends, which cuts across every layer by definition (Vertical Slices).
- The one horizontal thing that is legitimately its own step is the expand phase of a migration: adding a nullable column changes no behaviour, is deployable alone, and is trivially revertible (Expand and Contract).
- The flag is a boundary too, and a temporary one. A flag that outlives its slice becomes permanent branching in the state space, which is a cost nobody budgets (Feature Flags and What They Cost).
A slice is not a layer
The distinction is not about size, and it is not about folders. It is about whether the increment, on its own, changes what somebody can do. A layer changes what the code contains; a slice changes what the system does.
The diagram below is the whole lesson. The left plan has three merged pull requests and zero delivered behaviour; the right has one merged pull request and support have stopped editing the database by hand. Both are described as "week one done" in a status update, which is why this survives.
- A slice can be described as "someone can now do X". A layer can only be described as "the code now contains Y".
- A slice is revertible on its own. Reverting the domain-model layer with the API layer merged leaves a build that does not compile.
- A slice carries its own migration, its own tests and its own flag. If it borrows any of those from a future slice, it is not one.
- The legitimate exception is the expand phase of a migration: a nullable column changes no behaviour and is deployable and revertible alone (Expand and Contract).
Three slices, and what each one buys
Notice what slice one includes and excludes. It includes the billing job, because the interaction between pause and billing is the risky unknown and putting it in week one is the entire point. It excludes the UI, auto-resume and email, because none of them is needed for the behaviour to be true.
It is also genuinely useful on its own: support stop editing rows by hand, which is a real reduction in the chance of somebody fat-fingering a subscription id at eleven at night. A first slice that is useful to nobody is a layer with better marketing.
- 1Slice 1 — support can pause
Expand migration, state model, internal endpoint, billing job honours the state, invariant test. Released to support only.
fails by Being scoped to "the model and the schema", which delivers nothing and leaves the billing overlap undiscovered until week three.
- 2Slice 2 — customers can pause
Public endpoint with an idempotency key, UI, limits and guards, behind a flag released to 5% then everyone.
fails by Shipping the UI before the endpoint is idempotent, so the first double-click in production becomes the incident that teaches you (Idempotency by Design).
- 3Slice 3 — pauses end by themselves
Resume date, scheduled resume, confirmation email from the committed event, flag removed and code deleted.
fails by Leaving the flag in place "for safety", which makes the branch permanent and doubles the state space every future feature must reason about.
Each row is deployable on the Thursday it is finished and revertible on the Friday it is not. The plan survives the feature being cancelled after any row, which is the property that layering cannot offer at any price.
How thin is too thin
Slicing has a floor. Below a certain size, each slice pays a fixed cost — a deploy, a flag, a migration review, a round of QA — that exceeds the work inside it, and the team ends up optimising a burndown chart rather than shipping. The decision below is about finding that floor honestly.
The failure to watch for is subtler than slices being too thick or too thin: it is slices that are shaped like layers and named like slices. That one is worth naming as a smell, because it passes every planning review.
looks like A plan of pull requests titled "part 1: schema", "part 2: service layer", "part 3: API", "part 4: UI" — described in the standup as a vertical slice plan because the work was broken up.
suggests No increment is releasable, all the risk is in part four, and the first real feedback arrives after the schema is expensive to change. The status board shows 75% complete with 0% delivered.
fix Ask of each piece: who can do something new once this is in production? If the answer is nobody for three pieces running, re-cut the plan so the first piece is the thinnest complete path — usually an internal endpoint plus the one integration that carries the risk.
Can each piece be released on its own, and is it worth something to somebody?
when The feature is uncertain, the domain is unfamiliar, or the risky interaction is with existing code.
cost More deploys, more flags, slightly more total effort, and a schema that has to be designed whole while being delivered in pieces.
when The design is well understood and the team is coordinating across services, where per-slice overhead is high.
cost Later feedback and a bigger revert unit. Usually the right answer at scale, and often mislabelled as a failure of discipline.
when The feature is certain to ship whole, the domain is familiar, and there is no useful intermediate state — a protocol implementation, an algorithm rewrite.
cost All risk in the final deploy and no option to stop early. Legitimate, and much rarer than it is claimed to be.
when The change is genuinely small, or coexistence of old and new is impossible.
cost Nothing, if it really is small. The trap is that "it is small" is an estimate, and this option is the one where being wrong is most expensive (Change Size: Why Small Changes Are Safer, and When They Are Not).
How to build it
Most important first.
- Find the narrowest path that is genuinely useful to someone. For pause, it is: support can pause one subscription via an internal endpoint, and the billing job respects it. No UI, no auto-resume, no email — and it removes the manual database edits support does today, which is real value on day three.
- Make each slice cross every layer it needs and no more. The first slice touches schema, domain, one endpoint and the billing job, because that is the thinnest thing that can be true end to end.
- Put the risky question in slice one. The overlap with the nightly job is the thing most likely to invalidate the design, so it should be answered in week one and not week three (Reversible and Irreversible Decisions).
- Guard each slice so it can be released to nobody, then to support, then to everyone. Deploying and releasing become separate decisions, which is what makes weekly shipping safe rather than brave (Progressive Delivery: Exposure as a Dial).
- Sequence so each slice is revertible without the next. If slice two requires slice three to be coherent, they are one slice that you have written down as two.
- Delete the flag as part of the slice that completes it, in the same sprint. Cleanup scheduled for "later" is scheduled for never, and the debt is a permanently larger state space (Deliberate Debt).
What the next change costs
The field this whole domain exists for. A structure is only better if it makes the change after this one cheaper — and it is worth saying which changes it does not help.
- Under slices, cancelling the feature in week two costs the unbuilt slices and keeps the shipped ones. Support keeps the internal pause endpoint, which was the thing that removed manual database edits.
- Under layers, cancelling in week two costs everything, plus a schema change in production that nothing reads and nobody will dare drop.
- A change of direction in week two — "actually it should pause at period end" — costs one slice's rework under slicing, and a redesign of a domain model built for a different rule under layering.
- What slicing does not make cheaper: total effort. It is usually slightly more, because each slice pays its own release, test and flag overhead. What it buys is optionality and earlier information, and if neither is worth anything on this feature then layering is the cheaper plan (The Cost of Change).
- Slicing costs more total work: more deploys, more flags, more migrations, each with its own overhead. You are buying optionality with effort, and on a feature that is certain to ship whole, that purchase is a loss.
- Thin slices can produce user-visible half-features, which is worse than nothing for a customer-facing surface. That is what flags are for, and flags are themselves a cost.
- It requires the schema to be designed as a whole even though it is delivered in pieces, which is a genuine tension — you are deferring code decisions while committing to data ones (Data Migration).
What can go wrong
- The slices are layers with new names. The tell is that none of the first three can be described in terms of a user doing something.
- Slice one ships and slice two is deprioritised, leaving a half-feature that support has learned to work around and nobody owns.
- Flags accumulate. Six months later the state space is 2^7 combinations, of which four are tested, and no one can say what the product does (Feature Flags and What They Cost).
- Each slice gets its own migration, and the fourth conflicts with the second, because migrations were sliced but the schema was not designed as a whole (Data Migration).
- The mitigation fails too: a team that slices religiously starts shipping slices so thin that each carries more release overhead than implementation, and velocity drops while the burndown looks excellent.
- Slices depend on each other in sequence, but only forward: slice three may assume slice two shipped, and slice two must never assume slice three will.
- Every slice depends on the migration strategy, because a slice that needs an atomic schema-and-code deploy is not independently releasable (Data Migration).
- The approach depends on being able to release without a marketing moment. Where a feature must launch whole — a public announcement, a partner integration — slicing still governs the work, but release stays a single event and the flag does more of the job.
- "A slice is a small PR." Size is not the property. A one-line PR that adds an unused column is a layer; a four-hundred-line PR that lets support pause a customer end to end is a slice.
- "Slicing means no shared foundations." It means foundations are built by the slice that first needs them, and generalised by the second. That is The Rule of Three applied to infrastructure (Premature Abstraction).
- "Vertical slices mean vertical folders." Related but separate: how you slice the work and how you organise the files are different decisions, and either can be done without the other (Vertical Slices).
- "Every slice must be visible to customers." Every slice must be *releasable* and useful to someone. An internal support endpoint is a perfectly good slice and is often the best first one.
- shotgun-surgery
Testing it, and how it ages
- Each slice ships its own tests, at the boundary it actually created. A slice with no test is not a slice, because it cannot be reverted with confidence.
- The invariant test — a paused subscription is never charged — is written in slice one and never touched again. It is the one assertion that spans every slice (Where a Test Must Be Real).
- Test the flag-off path as well as the flag-on path. Half of production is running the off path and it is the one nobody exercises locally (Feature Flags and What They Cost).
- Do not write tests for a slice that has not shipped. Tests written against an imagined future slice are the same speculative bet as the code would be (YAGNI, With Its Bill Attached).
- Slices leave a trail of small, revertible commits, which is what makes the feature bisectable a year later when something about pause is subtly wrong (Debuggability by Design).
- The slice boundaries tend to become the module boundaries, because each slice was the thinnest complete thing and thin complete things are what modules should be (Package by Feature).
- The approach stops fitting when a change genuinely cannot be made incrementally — a storage engine swap, a protocol change with no coexistence story. Those exist, they are rarer than people claim, and the honest answer there is expand-and-contract at a larger grain rather than a big-bang cutover (Incremental Migration).
Where this applies
This domain's advice is contested more than most. These labels say what each claim is specific to — and where CONTESTED appears, the note gives the strongest form of the opposing view rather than a caricature.
- GENERALThat an increment delivering no observable behaviour cannot be validated or reverted independently follows from what "releasable" means, so it holds across stacks. What varies is how cheap the guard is: a flag in a server-rendered app is one condition, and in a mobile app shipped through review it is a remote config plus a release cycle.
- SCALE-SPECIFICAt two engineers on a weekly train, slicing is nearly free and mostly a planning habit. At forty engineers across six teams, a slice has to be coordinated across services and the per-slice overhead — flags, contracts, deploy ordering — rises fast enough that slices get chunkier, which is a real and legitimate adaptation rather than a failure of discipline.
- CONTESTEDThe strongest opposing view is that horizontal-first is genuinely cheaper when the design is well understood: building the schema and the domain model once, whole, avoids the rework of growing them slice by slice, avoids flag debt entirely, and produces a more coherent model than one accreted from four thin paths. That is a real effect and it is right when the feature is certain to ship in full and the domain is familiar — it fails when either assumption is wrong, and both are wrong more often than teams estimate.
Where the depth lives
This domain teaches the codebase-level structure and hands the rest off.
- — Testing & Reliability Engineering — a slice is only revertible if you can tell it broke something, which is a detection question rather than a design one.