Feature slicing
Introduction#
The way that work is broken down into deliverable chunks has an enormous effect on the teams’ effectiveness. When breaking down a feature, always try to split it into smaller features — that is, pieces that represent vertical slices of functionality rather than horizontal technology layers.
This approach ensures a focus on delivering user and business value, but while the concept is simple, the skill of consistently splitting work into vertical slices takes determination and experience to develop. All team members should actively contribute to story splitting, but the Tech Lead and Product Owner have a particular responsibility to set the pattern for this.
This page defines these terms, explains the benefits of this approach and provides advice on how to do this in practice.
Key points
- Breaking work into pieces that represent vertical slices ensures a focus on delivering user and business value.
- Vertical slices are user-visible, user-valuable, and cut through architectural layers.
- A vertical slice only needs to take the system a tiny step in the right direction, it does not need to represent a complete feature that is ready to give to users.
- Items such as Epics, Features, and User Stories should always be vertical slices.
- Progressively splitting items into finer vertical slices releases more value earlier, provides more frequent feedback and therefore learning, and reduces risk.
- The vertical slicing approach is in line with the widely used Invest pattern.
Definition of a vertical slice#
To qualify as a true vertical slice, all of the following must be true.
A vertical slice is user-visible, user-valuable, and cuts through architectural layers.
The result must be user-visible. If the user can’t see a change in the system after a story is implemented, then we have failed to produce a meaningful change for them.
It must be user-valuable. The difference must be moving in a direction the user sees as valuable. If we fail to do this then we have misunderstood what the user wanted or they have changed their mind. Either way, we have learned something useful. Small slices allow us to learn from this kind of feedback and course-correct frequently.
It should cut through architectural layers. We favour implementing a tiny amount of user-visible functionality through the stack over functionality that is limited to only part of the stack. Until we have traversed the full stack, we are storing up unknowns that represent risk.
Examples#
To illustrate the difference between vertical and horizontal slices, we consider some simple examples.
Example 1: Registration page
Imagine we are breaking down a large story for user registration:
As a new user, I want to register for the site so that I can save my cart and buy items.
If we break it down into task-like items, we end up with horizontal slices.
For example:
- Design UI form
- Build registration form with no API calls
- Implement address lookup
- Implement registration API
- Add Users table to database schema
- Write new user details to database
No user value is released until all of the horizontal slices are complete.
Instead, we could split it into smaller stories, each of which represents a vertical slice of functionality. This releases user value early and continuously.
- New blank registration form
- User can register with forename and surname
- User can enter address manually during registration
- User can enter address via postcode lookup during registration
- User can set product preferences during registration
Example 2: Scheduling API
We start with a large story for a scheduling API:
As a job scheduler, I want to have my jobs automatically scheduled so that I do not have to do it manually.
This could be split horizontally as:
- Map request parameters to domain objects.
- Map domain objects to scheduling engine query object.
- Send request to scheduling engine.
- Parse response from scheduling engine.
- Formulate API response.
But as with most horizontal slicing, we get no value until the last part is complete, and we cannot do any end-to-end testing. So we a deprived of feedback and valuable learning until the end. Better, we can split vertically as:
- Schedule a single job (mapping request, sending to engine and parsing response, and responding to the requester).
- Simplifications: One person, no skills matching.
- Schedule first day of multiple jobs.
- Schedule all jobs over multiple days.
- Simplifications: allow as many days as required.
- Allow scheduling period to be truncated.
- Schedule jobs for multiple people (no restrictions)
- Simplifications: no skills matching, all people have same home location, ignore engine request limits.
- Match jobs to people skills.
- Optimise for each person having different home location.
- Page requests to scheduling engine to avoid exceeding limits.
Example 3: Flight search
We start with a large story for a flight search page:
As a holidaymaker, I want to search for flights so that I can find cheap and convenient options.
This could be split vertically as:
- Blank Hello World page.
- Origin and destination fields; results are simply echoed back when submitted.
- Support searching for flights on one route: London to Edinburgh.
- Simplifications: One person, direct flights only, single carrier, within country so single currency and no time differences, automatic seat allocation.
- Support searching for flights between 5 major cities within same country.
- Support multiple passengers.
At this point, you may be able to go live with early adopters who only need the features already available. As soon as you reach this threshold, you can reprioritise the remaining feature slices based on feedback.
Maybe your target market is happy with the small selection of cities you support, but it’s important to them to be able to search all available carriers.
- Support one additional carrier — one such slice for each available carrier.
They may not like automatic seat allocation:
- Choose seats.
Or perhaps there is one main carrier your customers care about but they want more choice of cities:
- Support 10 additional locations.
Perhaps they really want to fly abroad:
- Support flights that cross time zones.
And so on. The benefit of slicing finely like this is it opens up a wide range of options for the order in which incremental enhancements are made, and the benefit of going live early with a basic product is that you can learn which of these features are most valuable to implement first.
In each example, the first stories lay simple foundations for the rest, sometimes called a walking skeleton or steel thread. Once complete, the other stories can be tackled in various orders, but until that time they are blocked. To avoid the team waiting idle while those initial stories are implemented, they could swarm on them. They can do this by breaking them into even smaller tasks, this time along horizontal layers since they have gone as far as they can splitting vertically. These tasks can be implemented in parallel by multiple people, each working to agreed interfaces between the layers. Another way to parallelise work on even a single task in a small story is to pair, possibly using adversarial test-driven development ↗.
Questions to ask#
Adopting the feature slicing mindset means interrogating every user story to see how it can be dissected into smaller slices of functionality. Here are some examples of the questions you might ask. These questions are illustrative and certainly not exhaustive.
- What’s the simplest thing we can start with? We’re looking for a functional vertical slice that barely does anything. This is often an empty form, API response or similar and is sometimes called a Hello World slice. Although the amount of user value produced is miniscule, it is not zero; and it produces a useful amount of learning value because it forces you to exercise several integration points.
- Can we start by allowing reads or gets but not writes? Retrieving and displaying information is often simpler than allowing it to be modified.
- Can we start with a simplified use case? For example, if we know we need to schedule multiple jobs we still may start by supporting only one job.
- Can we defer automation? For example, we may know that the ability to look up an address is considered essential by users but starting with manual address entry is still a useful functional increment.
- Can we defer authentication? Even if authentication will be essential for a releasable feature, a naive implementation that lacks authentication can represent a valid slice of functionality.
- Can we defer input validation? Of course, validation will need to be added before the feature is ready to be released to users, but even processing inputs without validation represents a useful step forward and represents a valid slice.
- Can we display data unstyled? While we will want to make our UI look nice before releasing it, we can defer this and implement a slice that just shows the data in a raw form. The amount of rework this incurs is tiny.
- Can we defer performance optimisations? Sometimes we know that a naive implementation will work and move us usefully forward, even if we know it won’t be acceptable for a production release. A good example is introducing paging for API requests. However, at other times we cannot be confident that our approach will scale, and in these cases non-functional testing is essential to guide implementation and should not be separated into a separate functional slice.
A note on testing
Functional testing should always be done as part of implementing each slice and never separated into a separate backlog item.
In contrast, it is often sensible to defer non-functional testing until a minimum useful feature set is present, which will likely consist of multiple slices. But do not defer it for too long. Non-functional testing should still be a regular activity throughout development. And some slices will introduce functionality that carries an obvious risk of performance or scalability issues. In these cases, testing is essential to validate the implementation approach and should not be separated out.
Benefits of slicing vertically#
We can clearly see the benefits of small vertical slices by comparing this approach with the other extreme of traditional waterfall delivery.
In waterfall delivery, no value is released until a big bang go live at the end. The resulting system may be what the user asked for, but almost certainly includes functionality that (they now realise) is of relatively low value to them and omits functionality that they have discovered is of high value to them. Because we have waited until the end to get meaningful user feedback — the kind that only comes through the user interacting with real working software — we and our users have missed the opportunity to feel out what the most valuable functionality is.
By breaking goals down into User Stories that represent vertical slices of functionality, value is released in chunks, and rapid feedback is possible. Each time we deliver a piece of functionality, our users get to see how the features they asked for really work. This is a learning experience for us and our users, with assumptions being tested and refined. This iterative steering toward the next highest value features results in value being generated more quickly.
The focus on delivering working software early also forces us to tackle technical challenges that we may assume are easy but could turn out to be difficult. Again, assumptions are tested and refined, reducing risk.
All of these factors are multiplied as the slices of functionality become smaller. Small stories release the most value earliest and create the most value overall.
These differences can be visualised as cumulative value curves.
The value shown in this graph is a combination of learning value, which accumulates as we better understand user needs, technical feasibility and any dependencies, and user value, created through the addition of useful features to the system.
At the start, learning value accumulates rapidly, but direct user value does so somewhat slower. Over time, this balance shifts: once the biggest unknowns are understood we are able to make more rapid progress on delivering features that users need. The rate of value generation eventually slows, enabling a choice to be made between continuing to iterate on this system or switching to other work if that is of higher priority. The relentless focus on delivering what is most valuable in each increment buys us the option to stop early, knowing that most of the value has already been delivered.
The Agile Manifesto ↗ and its twelve principles ↗ are still highly relevant today and capture the above succinctly.
Agile manifesto (selected excerpt)
Working software over comprehensive documentation.
Customer collaboration over contract negotiation.
Responding to change over following a plan.
Invest#
The vertical slicing approach tallies with the Valuable and Small aspects of the Invest ↗ model.
Invest
- (I)ndependent
- (N)egotiable
- (V)aluable
- (E)stimable
- (S)mall
- (T)estable
Although it is implicit in that model that “valuable” implies “user-visible,” this is often misunderstood, hence the focus on this point here.
In practice#
A great way to develop vertical slicing skills is to play Elephant Carpaccio ↗, a game developed by Henrik Kniberg and Alistair Cockburn.
Tips
- Be vigilant for User Stories that fail the simple test of creating a change that is user-visible, user-valuable, and cuts through all relevant architecture layers.
- As well as User Stories, Tasks and even individual commits should ideally also be vertical slices, but this is not always feasible.
- Remember, a slice is valuable even if it only moves the system a small step toward an outcome that users will find valuable. It does not need to have reached a point where the feature is sufficiently complete to be usable, it just needs to be moving in the right direction.
- Delivering in vertical slices does not mean you have to expose features to users in the live system until they are ready. Feature flags are one mechanism to allow partially complete features to be deployed to production without becoming visible to users.
- Not all work can be usefully expressed as User Stories. For example, refactoring an area of the codebase may be a valuable task but it is not useful to try to write it in user story form. However, with careful attention, most work can be viewed in terms of user benefit, and doing so forces a focus on value that can be highly informative.