These anti-patterns affect how work moves through the team. They create bottlenecks, hide problems, and prevent the steady flow of small changes that continuous delivery requires.
This is the multi-page printable view of this section. Click here to print.
Team Workflow
- 1: Horizontal Slicing
- 2: Monolithic Work Items
- 3: Unbounded WIP
- 4: Knowledge Silos
- 5: Big-Bang Feature Delivery
- 6: Undone Work
- 7: Push-Based Work Assignment
1 - Horizontal Slicing
Category: Team Workflow | Quality Impact: Medium
What This Looks Like
The team breaks a feature into work items by technical layer. One item for the database schema. One for the API. One for the UI. Maybe one for “integration testing” at the end. Each item lives in a different lane or is assigned to a different specialist. Nothing reaches production until the last layer is finished and all the pieces are stitched together.
In distributed systems this gets worse. A feature touches multiple services owned by different teams. Instead of slicing the work so each team can deliver their part independently, the teams plan a coordinated release. Team A builds the new API, Team B updates the UI, Team C modifies the downstream processor. All three deliver “at the same time” during a release window, and the integration is tested for the first time when the pieces come together.
Common variations:
- Layer-based assignment. “The backend team builds the API, the frontend team builds the UI.” Each team delivers their layer independently. Integration is a separate phase that happens after both teams finish.
- The database-first approach. Every feature starts with “build the schema.” Weeks of database work happen before any API or UI exists. The schema is designed for the complete feature rather than for the first thin slice.
- The API-then-UI pattern. The API is built and “tested” in isolation with Postman or curl. The UI is built weeks later against the API. Mismatches between what the API provides and what the UI needs are discovered at the end.
- The cross-team integration sprint. Multiple teams build their parts of a feature independently, then dedicate a sprint to wiring everything together. This sprint always takes longer than planned because the teams built on different assumptions about contracts and data formats.
- Technical stories on the board. The backlog contains items like “create database indexes,” “add caching layer,” or “refactor service class.” None of these deliver observable behavior. They are infrastructure work that has been separated from the feature it supports.
The telltale sign: a team cannot deploy their changes until another team deploys theirs first, or until a coordinated release window.
Why This Is a Problem
Horizontal slicing feels natural because it matches how developers think about the system’s architecture. But it optimizes for how the code is organized, not for how value is delivered. The consequences compound in distributed systems where cross-team coordination multiplies every delay.
It reduces quality
A horizontal slice delivers no observable behavior on its own. The schema alone does nothing. The API alone does nothing a user can see. The UI alone has no data to display. Value only emerges when all layers are assembled, and that assembly happens at the end.
When teams in a distributed system build their layers in isolation, each team makes assumptions about how their service will interact with the others. These assumptions are untested until integration. The longer the layers are built separately, the more assumptions accumulate and the more likely they are to conflict. Integration becomes the riskiest phase, the phase where all the hidden mismatches surface at once.
With vertical slicing, integration happens with every item. The first slice forces the developer to verify the contracts between services immediately. Assumptions are tested on day one, not month three.
It increases rework
A team that builds a complete API layer before any consumer touches it is guessing what the consumer needs. When the UI team (or the upstream service team) finally integrates, they discover the response format does not match, fields are missing, or the interaction model is wrong. The API team reworks what they built weeks ago.
In a distributed system, this rework cascades. A contract mismatch between two services means both teams rework their code. If a third service depends on the same contract, it reworks too. A single misalignment discovered during a coordinated integration can send multiple teams back to revise work they considered done.
Vertical slicing surfaces these mismatches immediately. Each slice forces the real contract to be exercised end-to-end, so misalignments are caught when the cost of change is low: one slice, not an entire layer.
It makes delivery timelines unpredictable
Horizontal slicing creates hidden dependencies between teams. Team A cannot ship until Team B finishes their layer. Team B is blocked on Team C’s schema change. Nobody knows the real delivery date because it depends on the slowest team in the chain.
Vertical slicing within a team’s domain eliminates cross-team delivery dependencies. Each team decomposes work so that their changes are independently deployable. The team ships when their slice is ready, not when every other team’s slice is ready.
It creates coordination overhead that scales poorly
When features require a coordinated release across teams, the coordination effort grows with the number of teams involved. Someone has to schedule the release window. Someone has to sequence the deployments. Someone has to manage the rollback plan when one team’s deployment fails. This coordination tax is paid on every feature, and it grows as the system grows.
Teams that slice vertically within their domains can deploy independently. They define stable contracts at their service boundaries and deploy behind those contracts without waiting for other teams. The coordination cost drops to near zero because the interfaces (not the release schedule) handle the integration.
Impact on continuous delivery
CD requires a steady flow of small, independently deployable changes. Horizontal slicing produces the opposite: batches of interdependent layer changes that can only be deployed together after a separate integration phase.
A team that slices horizontally cannot deploy continuously because there is nothing to deploy until all layers converge. In distributed systems, this gets worse because the team cannot deploy until other teams converge too. The deployment unit grows from “one team’s layers” to “multiple teams’ layers,” and the risk grows with it.
Vertical slicing is what makes independent deployment possible. Each slice delivers complete behavior within the team’s domain, exercises real contracts with other services, and can move through the pipeline on its own.
How to Fix It
Step 1: Learn to recognize horizontal slices
Review the current sprint board and backlog. For each work item, ask:
- Can a user or another service observe the change after this item is deployed?
- Can the team deploy this item without waiting for another team?
- Does this item deliver behavior, or does it deliver a layer?
If the answer to any of these is no, the item is likely a horizontal slice. Tag these items and count them. Most teams discover that a majority of their backlog is horizontally sliced.
Step 2: Map your team’s domain boundaries
In a distributed system, the team does not own the entire feature. They own a domain. Identify what services, data stores, and interfaces the team controls. The team’s vertical slices cut through the layers within their domain, not through the entire system.
How “end-to-end” is defined depends on what your team owns. A full-stack product team owns the entire user-facing surface from UI to database; their slice is done when a user can observe the behavior. A subdomain product team owns a service boundary; their slice is done when the API contract satisfies the agreed behavior for consumers. The Work Decomposition guide covers both contexts with diagrams.
For each service the team owns, identify the contracts other services depend on. These contracts are the boundaries that enable independent deployment. If the contracts are not explicit (no schema, no versioning, no documentation), define them. You cannot slice independently if you do not know where your domain ends and another team’s begins.
Step 3: Reslice one feature vertically within your domain
Pick one upcoming feature and practice reslicing it:
Before (horizontal):
- Add new columns to the orders table
- Build the discount calculation endpoint
- Update the order summary UI component
- Integration testing across services
After (vertical, within team’s domain):
- Apply a percentage discount to a single-item order (schema + logic + contract)
- Apply a percentage discount to a multi-item order
- Reject an expired discount code with a clear error response
- Display the discount breakdown in the order summary (UI service)
Each slice is independently deployable within the team’s domain. The UI service (item 4) treats the order service’s discount response as a contract. It can be built and deployed separately once the contract is defined, just like any other service integration.
Step 4: Treat the UI as a service
The UI is not the “top layer” that assembles everything. It is a service that consumes contracts from other services. Apply the same principles:
- Define the contract. The UI depends on API responses with specific shapes. Make these contracts explicit. Version them. Test against them with contract tests.
- Deploy independently. The UI service should be deployable without coordinating with backend service deployments. If it cannot be, the coupling between the UI and backend is too tight.
- Slice vertically within the UI. A UI change that adds a new widget is a vertical slice if it delivers complete behavior. A UI change that “restructures the component hierarchy” is a horizontal slice.
When the UI is loosely coupled to backend services through stable contracts, UI teams and backend teams can deploy on their own schedules. Feature flags in the UI control when new behavior is visible to users, independent of when the backend capability was deployed.
Step 5: Use contract tests to enable independent delivery
In a distributed system, the alternative to coordinated releases is contract testing. Each team verifies that their service honors the contracts other services depend on:
- Provider tests verify that your service produces responses matching the agreed contract.
- Consumer tests verify that your service correctly handles the responses it receives.
When both sides test against the shared contract, each team can deploy independently with confidence that integration will work. The contract (not the release schedule) guarantees compatibility.
Step 6: Make the deployability test a refinement habit
For every proposed work item, ask: “Can the team deploy this item on its own, without waiting for another team or another item to be finished?”
If not, the item needs reslicing. This single question catches most horizontal slices before they enter the sprint.
| Objection | Response |
|---|---|
| “Our developers are specialists. They can’t work across layers.” | That is a skill gap, not a constraint. Pairing a frontend developer with a backend developer on a vertical slice builds the missing skills while delivering the work. The short-term slowdown produces long-term flexibility. |
| “The database schema needs to be designed holistically” | Design the schema incrementally. Add the columns and tables needed for the first slice. Extend them for the second. This is how trunk-based database evolution works - backward-compatible, incremental changes. |
| “We can’t deploy without the other team” | That is a signal about your service contracts. If your deployment depends on another team’s deployment, the interface between the services is not well defined. Invest in explicit, versioned contracts so each team can deploy on its own schedule. |
| “Vertical slices create duplicate work across layers” | They create less total work because integration problems are caught immediately instead of accumulating. The “duplicate” concern usually means the team is building more infrastructure than the current slice requires. |
| “Our architecture makes vertical slicing hard” | That is a signal about the architecture. Services that cannot be changed independently are a deployment risk. Vertical slicing exposes this coupling early, which is better than discovering it during a high-stakes coordinated release. |
Measuring Progress
| Metric | What to look for |
|---|---|
| Percentage of work items that are independently deployable | Should increase toward 100% |
| Time from feature start to first production deploy | Should decrease as the first vertical slice ships early |
| Cross-team deployment dependencies per feature | Should decrease toward zero |
| Development cycle time | Should decrease as items no longer wait for other layers or teams |
| Integration frequency | Should increase as deployable slices are completed and merged daily |
Related Content
- Work Decomposition - The practice guide for vertical slicing techniques, including how the approach differs for full-stack product teams versus subdomain product teams in distributed systems
- Small Batches - Vertical slicing is how you achieve small batch size at the story level
- Work Items Take Too Long - Horizontal slices are often large because they span an entire layer
- Trunk-Based Development - Vertical slices enable daily integration because each is independently complete
- Architecture Decoupling - Loose coupling between services enables independent vertical slicing
- Team Alignment to Code - Organizing teams around domain boundaries rather than layers removes the structural cause of horizontal slicing
2 - Monolithic Work Items
Category: Team Workflow | Quality Impact: High
What This Looks Like
The product owner describes a feature. The team discusses it briefly. Someone creates a ticket with the feature title - “Add user profile page” - and it goes into the backlog. When a developer pulls it, they discover it involves a login form, avatar upload, email verification, notification preferences, and password reset. The ticket is one item. The work is six items.
Common variations:
- The feature-as-ticket. Every work item maps to a user-facing feature. There is no breakdown step between “product wants this” and “developer builds this.” Items are estimated at 8 or 13 points without anyone questioning whether they should be decomposed.
- The spike that became a feature. A time-boxed investigation turns into an implementation because the developer has momentum. The result is a large, unplanned change that was never decomposed or estimated.
- The acceptance criteria dump. A single ticket has 10 or more acceptance criteria. Each criterion is an independent behavior that could be its own item, but nobody splits them because the feature “makes sense as a whole.”
- The refinement skip. The team does not have a regular refinement practice, or refinement consists of estimation without decomposition. Items enter the sprint at whatever size the product owner wrote them.
The telltale sign: items regularly take five or more days from start to done, and the team treats this as normal.
Why This Is a Problem
Without decomposition, work items are too large to flow through the delivery system efficiently. Every downstream practice - integration, review, testing, deployment - suffers.
It reduces quality
Large items hide unknowns. A developer makes dozens of decisions over several days in isolation. Nobody sees those decisions until the code review, which happens after all the work is done. When the reviewer disagrees with a choice made on day one, five days of work are built on top of it. The team either rewrites or accepts a suboptimal decision because the cost of changing it is too high.
Small items surface decisions quickly. A one-day item produces a small PR that is reviewed within hours. Fundamental design problems are caught early, before layers of code are built on top.
It increases rework
Large items create large pull requests. Large PRs get superficial reviews because reviewers do not have time to review 300 lines carefully. Defects that a thorough review would catch slip through. The defects are discovered later - in testing, in production, or by the next developer who touches the code - and the fix costs more than it would have if the work had been reviewed in small increments.
It makes delivery timelines unpredictable
A large item estimated at five days might take three days or three weeks depending on what the developer discovers along the way. The estimate is a guess. Plans built on large items are unreliable because the variance of each item is high.
Small items have narrow estimation variance. Even if the estimate is off, it is off by hours, not weeks.
Impact on continuous delivery
CD requires small, frequent changes flowing through the pipeline. Large work items produce the opposite: infrequent, high-risk changes that batch up in branches and land as large merges. A team working on five large items has zero deployable changes for days at a time.
Work decomposition is the practice that creates the small units of work that CD needs to flow.
How to Fix It
Step 1: Establish the 2-day rule
Agree as a team: no work item should take longer than two days from start to integrated on trunk. This is a constraint on item size, not a velocity target. When an item cannot be completed in two days, decompose it before pulling it into the sprint.
Step 2: Decompose during refinement
Build decomposition into the refinement process:
- Product owner presents the feature or outcome.
- Team writes acceptance criteria in Given-When-Then format.
- If the item has more than three to five criteria, split it.
- Each resulting item is estimated. Any item over two days is split again.
- Items enter the sprint already small enough to flow.
Step 3: Use acceptance criteria as splitting boundaries
Each acceptance criterion or small group of criteria is a natural decomposition boundary:
Each scenario can be implemented, integrated, and deployed independently.
Step 4: Combine with vertical slicing
Decomposition and vertical slicing work together. Decomposition breaks features into small pieces. Vertical slicing ensures each piece cuts through all technical layers to deliver complete functionality. A decomposed, vertically sliced item is independently deployable and testable.
| Objection | Response |
|---|---|
| “Splitting creates too many items” | Small items are easier to manage. They have clear scope, predictable timelines, and simple reviews. |
| “Some things can’t be done in two days” | Almost anything can be decomposed further. Database migrations can be backward-compatible steps. UI changes can hide behind feature flags. |
| “Product doesn’t want partial features” | Feature flags let you deploy incomplete features without exposing them. The code is integrated continuously, but the feature is toggled on when all slices are done. |
Measuring Progress
| Metric | What to look for |
|---|---|
| Item cycle time | Should be two days or less from start to trunk |
| Development cycle time | Should decrease as items get smaller |
| Items completed per week | Should increase |
| Integration frequency | Should increase as developers integrate daily |
Related Content
- Work Decomposition - The practice guide for breaking work into small increments
- Horizontal Slicing - Decomposition without vertical slicing still produces items that cannot flow independently
- Small Batches - Batch size reduction at every level
3 - Unbounded WIP
Category: Team Workflow | Quality Impact: High
What This Looks Like
The team’s board has no column limits. Developers pull new items whenever they feel ready - when they are blocked, waiting for review, or simply between tasks. Nobody stops to ask whether the team already has too much in flight. The number of items in progress grows without anyone noticing because there is no signal that says “stop starting, start finishing.”
Common variations:
- The infinite in-progress column. The board’s “In Progress” column has no limit. It expands to hold whatever the team starts. Items accumulate until the sprint ends and the team scrambles to close them.
- The per-person queue. Each developer maintains their own backlog of two or three items, cycling between them when blocked. The team’s total WIP is the sum of every individual’s buffer, which nobody tracks.
- The implicit multitasking norm. The team believes that working on multiple things simultaneously is productive. Starting something new while waiting on a dependency is seen as efficient rather than wasteful.
The telltale sign: nobody on the team can say what the WIP limit is, because there is not one.
Why This Is a Problem
Without an explicit WIP constraint, there is no mechanism to expose bottlenecks, force collaboration, or keep cycle times short.
It reduces quality
When developers juggle multiple items, each item gets fragmented attention. A developer working on three things is not three times as productive - they are one-third as focused on each. Code written in fragments between context switches contains more defects because the developer cannot hold the full mental model of any single item.
Teams with WIP limits focus deeply on fewer items. Each item gets sustained attention from start to finish. The code is more coherent, reviews are smoother, and defects are fewer because the developer maintained full context throughout.
It increases rework
High WIP causes items to age. A story that sits at 80% done for three days while the developer works on something else requires context rebuilding when they return. They re-read the code, re-examine the requirements, and sometimes re-do work because they forgot where they left off.
Worse, items that age in progress accumulate integration conflicts. The longer an item sits unfinished, the more trunk diverges from its branch. Merge conflicts at the end mean rework that would not have happened if the item had been finished quickly.
It makes delivery timelines unpredictable
Little’s Law is a mathematical relationship: cycle time equals work in progress divided by throughput. If throughput is roughly constant, the only way to reduce cycle time is to reduce WIP. A team with no WIP limit has no control over cycle time. Items take as long as they take because nothing constrains the queue.
When leadership asks “when will this be done?” the team cannot give a reliable answer because their cycle time varies wildly based on how many items happen to be in flight.
Impact on continuous delivery
CD requires a steady flow of small, finished changes moving through the pipeline. Without WIP limits, the team produces a wide river of unfinished changes that block each other, accumulate merge conflicts, and stall in review queues. The pipeline is either idle (nothing is done) or overwhelmed (everything lands at once).
WIP limits create the flow that CD depends on: a small number of items moving quickly from start to production, each fully attended to, each integrated before the next begins.
How to Fix It
Step 1: Make WIP visible
Count every item currently in progress for the team, including hidden work like production bugs, support questions, and unofficial side projects. Write this number on the board. Update it daily. The goal is awareness, not action.
Step 2: Set an initial WIP limit
Start with N+2, where N is the number of developers. For a team of five, set the limit at seven. Add the limit to the board as a column constraint. Agree as a team: when the limit is reached, nobody starts new work. Instead, they help finish something already in progress.
Step 3: Enforce with swarming
When the WIP limit is hit, developers who finish an item have two choices: pull the next highest-priority item if WIP is below the limit, or swarm on an existing item if WIP is at the limit. Swarming means pairing, reviewing, testing, or unblocking - whatever helps the most important item finish.
Step 4: Lower the limit over time (Monthly)
Each month, consider reducing the limit by one. Each reduction exposes constraints that excess WIP was hiding - slow reviews, environment contention, unclear requirements. Fix those constraints, then lower again.
| Objection | Response |
|---|---|
| “I’ll be idle if I can’t start new work” | Idle hands are not the problem - idle work is. Help finish something instead of starting something new. |
| “Management will think we’re not working” | Track cycle time and throughput. Both improve with lower WIP. The data speaks for itself. |
| “We have too many priorities to limit WIP” | Having many priorities is exactly why you need a limit. Without one, nothing gets the focus needed to finish. |
Measuring Progress
| Metric | What to look for |
|---|---|
| Work in progress | Should stay at or below the team’s limit |
| Development cycle time | Should decrease as WIP drops |
| Items completed per week | Should stabilize or increase despite starting fewer |
| Time items spend blocked | Should decrease as the team swarms on blockers |
Related Content
- Limiting WIP - The practice guide for implementing WIP limits
- Small Batches - Reducing batch size reinforces low WIP
- Push-Based Work Assignment - Push assignment and missing WIP limits are mutually reinforcing
4 - Knowledge Silos
Category: Team Workflow | Quality Impact: Medium
What This Looks Like
When a bug appears in the payments module, the team waits for Sarah. She wrote most of it. When the reporting service needs a change, it goes to Marcus. He is the only one who understands the data pipeline. Pull requests for the mobile app wait for Priya because she is the only reviewer who knows the codebase well enough to approve.
Common variations:
- The sole expert. One developer owns an entire subsystem. They wrote it, they maintain it, and they are the only person the team trusts to review changes to it. When they are on vacation, that subsystem is frozen.
- The original author bottleneck. PRs are routed to whoever originally wrote the code, not to whoever is available. Review queues are uneven - one developer has ten pending reviews while others have none.
- The tribal knowledge problem. Critical operational knowledge - how to deploy, how to debug a specific failure mode, where the configuration lives - exists only in one person’s head. When that person is unavailable, the team is stuck.
- The specialization trap. Each developer is assigned to a specific area of the codebase and stays there. Over time, they become the expert and nobody else learns the code. The specialization was never intentional - it emerged from habit and was never corrected.
The telltale sign: the team’s capacity on any given area is limited to one person, regardless of team size.
Why This Is a Problem
Knowledge silos turn individual availability into a team constraint. The team’s throughput is limited not by how many people are available but by whether the right person is available.
It reduces quality
When only one person understands a subsystem, their work in that area is never meaningfully reviewed. Reviewers who do not understand the code rubber-stamp the PR or leave only surface-level comments. Bugs, design problems, and technical debt accumulate without the checks that come from multiple people understanding the same code.
When multiple developers work across the codebase, every change gets a review from someone who understands the context. Design problems are caught. Bugs are spotted. The code benefits from multiple perspectives.
It increases rework
Knowledge silos create bottlenecks that delay feedback. A PR waiting two days for the one person who can review it means two days of other work built on potentially flawed assumptions. When the review finally happens and problems are found, the rework is more expensive because more code has been built on top.
When any team member can review any code, reviews happen within hours. Problems are caught while the context is fresh and the cost of change is low.
It makes delivery timelines unpredictable
One person’s vacation, sick day, or meeting schedule can block the entire team’s work in a specific area. The team cannot plan around this because they never know when the bottleneck person will be unavailable. Delivery timelines depend on individual availability rather than team capacity.
Impact on continuous delivery
CD requires that the team can deliver at any time, regardless of who is available. Knowledge silos make delivery dependent on specific individuals. If the person who knows the deployment process is out, the team cannot deploy. If the person who can review a critical change is in a meeting, the change waits.
How to Fix It
Step 1: Map the knowledge distribution
Create a simple matrix: subsystems on one axis, team members on the other. For each cell, mark whether the person can work in that area independently, with guidance, or not at all. The gaps become visible immediately.
Step 2: Rotate reviewers deliberately
Stop routing PRs to the original author or designated expert. Configure auto-assignment to distribute reviews across the team. When a developer reviews unfamiliar code, they learn. The expert can answer questions, but the review itself is shared.
Step 3: Pair on siloed areas (Weeks 3-6)
When work comes in for a siloed area, pair the expert with another developer. The expert drives the first session, the other developer drives the next. Within a few pairing sessions, the second developer can work in that area independently.
Step 4: Rotate assignments (Ongoing)
Stop assigning developers to the same areas repeatedly. When someone finishes work in one area, have them pick up work in an area they are less familiar with. The short-term slowdown is an investment in long-term team capacity.
| Objection | Response |
|---|---|
| “It’s faster if the expert does it” | Faster today, but it deepens the silo. The next time the expert is unavailable, the team is blocked. Investing in cross-training now prevents delays later. |
| “Not everyone can learn every part of the system” | They do not need to be experts in everything. They need to be capable of reviewing and making changes with reasonable confidence. Two people who can work in an area is dramatically better than one. |
| “We tried rotating and velocity dropped” | Velocity drops temporarily during cross-training. It recovers as the team builds shared knowledge, and it becomes more resilient because delivery no longer depends on individual availability. |
Measuring Progress
| Metric | What to look for |
|---|---|
| Knowledge matrix coverage | Each subsystem should have at least two developers who can work in it |
| Review distribution | Reviews should be spread across the team, not concentrated in one or two people |
| Bus factor per subsystem | Should increase from one to at least two |
| Blocked time due to unavailable expert | Should decrease toward zero |
Related Content
- Slow Defect Resolution - Bugs take disproportionately long when only one person understands the domain
- Blocked Work Sits Idle - Blocked items that cannot be picked up because knowledge is too concentrated
- Domain Model Erosion - Codebase drift when domain understanding lives in too few people
- Repeated Domain Mistakes - Same errors recur when knowledge leaves with the people who held it
- Team Membership Changes Constantly - Roster changes that drain knowledge the team never externalized
- Code Review - Review practices that spread knowledge
- Working Agreements - Team norms for review rotation and pairing
- Push-Based Work Assignment - Push assignment reinforces silos by always sending the same work to the same person
5 - Big-Bang Feature Delivery
Category: Team Workflow | Quality Impact: High
What This Looks Like
The planning session produces a feature that will take four to six weeks to complete. The feature is assigned to two developers. For the next six weeks, they work in a shared branch, building the backend, the API layer, the UI, and the database migrations as one interconnected unit. The branch grows. The diff between their branch and main reaches 3,000 lines. Other developers cannot see their work because it is not merged until it is finished.
On completion day, the branch merge is a major event. Reviewers receive a pull request with 3,000 lines of changes across 40 files. The review takes two days. Conflicts with main branch changes have accumulated while the feature was in progress. Some of the code written in week one was made redundant by decisions made in week four, but nobody is quite sure which parts are now dead code. The merge happens. The feature ships. For a few hours, the team holds its breath.
From the outside, this looks like normal development. The feature is done when it is done. The alternative - delivering a feature in pieces - seems to require the feature to be “half shipped,” which nobody wants. So the team ships features whole. And each whole feature takes longer to build, longer to review, longer to test, longer to merge, and produces more production surprises than smaller, incremental deliveries would.
Common variations:
- The feature branch that lives for months. A feature with many components grows in a long-lived branch. By the time it is ready to merge, the branch has diverged significantly from main. Integration is a major project in itself.
- The “it’s not done until all parts are done” constraint. The team does not consider merging parts of a feature because the product owner or stakeholders define “done” as the complete, user-visible feature. Intermediate states are considered undeliverable by definition.
- The UI-last integration. Backend work is complete and merged. UI work is complete in a separate branch. The two halves are integrated at the end. Integration surfaces mismatches between what the backend provides and what the UI expects, late in the cycle.
- The “save it all for the big release” pattern. Multiple features are kept undeployed until they can be released together for marketing or business reasons. The deployment batch grows over weeks and is released in a single event.
The telltale sign: the word “feature” is synonymous with a unit of work that takes weeks and ships as a single deployment, and the team cannot describe how they would ship the same functionality in smaller pieces.
Why This Is a Problem
The size of a change determines its risk, its cost to review, its cost to debug, and its time in flight before reaching users. Big-bang feature delivery maximizes all of these costs simultaneously. Every property of a large change is worse than the equivalent properties of the same work done incrementally.
It reduces quality
Quality problems in a large feature have a long runway before discovery. A design mistake made in week one is not discovered until the feature is complete and tested - potentially five weeks later. By that point, the design decision has influenced every other component of the feature. Reversing it requires touching everything that was built on top of it.
Code review quality degrades with change size. A reviewer presented with a 50-line diff can give it detailed attention and catch subtle issues. A reviewer presented with a 3,000-line diff faces an impossible task. They will review the most prominent parts carefully and skim the rest. Defects in the skimmed sections reach production because reviews at that scale are necessarily superficial.
Test coverage is also harder to achieve for large features. Testing a complete feature as a unit means constructing test scenarios that span the full scope of the feature. Intermediate states - which may represent how the feature will actually behave under real usage patterns - are never individually tested.
Incremental delivery forces the team to define and verify quality at each increment. Each small merge is reviewable in detail. Each intermediate state is tested independently. Problems are caught when the affected code is fresh and the context is clear.
It increases rework
When a large feature reveals a problem at integration time, the scope of rework is proportional to the size of the feature. A misunderstanding about how a backend API should structure its response, discovered at the end of a six-week feature, requires changes to the backend, updates to the API contract, changes to the UI components consuming the API, and updates to any tests written against the original API shape. All of this work was built on a faulty assumption that could have been caught much earlier.
Large features also suffer from internal rework that never appears in the commit log. Code written in week one and refactored in week three represents work done twice. Approaches tried and abandoned in the middle of a large feature are invisible overhead. Teams underestimate the real cost of their large features because they do not account for the internal rework that happens before the feature is ever reviewed or tested.
Merge conflicts compound rework further. A feature branch that lives for four weeks will accumulate conflicts with the changes that other developers made during those four weeks. Resolving those conflicts takes time, and the resolution itself can introduce bugs. The longer the branch lives, the worse the conflict situation becomes - exponentially, not linearly.
It makes delivery timelines unpredictable
Large features hide risk until late in the cycle. The first three weeks of a six-week feature often feel like progress - code is being written, components are taking shape. The final week or two is where the risk surfaces: integration problems, performance issues, edge cases the design did not account for. The timeline slips because the risk was invisible during the planning and early development phases.
The “it’s done when it’s done” nature of big-bang delivery makes it impossible to give stakeholders accurate, current information. At three weeks into a six-week feature, the team may say they are “halfway done” - but “halfway done” for a large feature does not mean the first half is delivered and working. It means the second half is still entirely unknown risk.
Incremental delivery provides genuinely useful progress signals. When a vertical slice of functionality is deployed and working in production after one week, the team has delivered real value and has real data about what works and what does not. The remaining work is scoped against actual production behavior, not against a specification written before any code existed.
Impact on continuous delivery
Continuous delivery operates on the principle that small, frequent changes are safer than large, infrequent ones. Big-bang feature delivery is the inverse: large, infrequent changes that maximize blast radius. Every property of CD - fast feedback, small blast radius, easy rollback, predictable timelines - is degraded by large feature units.
CD also depends on the ability to merge to the main branch frequently. A feature that lives in a branch for four weeks is not being integrated continuously. The developer is integrating with a stale view of the codebase. When they finally merge, they are integrating weeks of drift all at once. The continuous in continuous delivery requires that integration happens continuously, not once per feature.
Feature flags make incremental delivery possible for complex features that cannot be user-visible until complete. The code merges continuously to main behind a flag. The feature is not visible to users until the flag is enabled. The delivery is continuous even though the user-visible release happens at a defined moment.
How to Fix It
Step 1: Distinguish delivery from release
Separate the concept of deployment from the concept of release. The most common objection to incremental delivery is “we cannot ship a half-finished feature to users” - but this conflates the two:
- Deployment means the code is running in production.
- Release means users can see and use the feature.
These are separable. Code can be deployed behind a feature flag, completely invisible to users, while the feature is built incrementally over several weeks. When the feature is complete, the flag is enabled. The release happens without a deployment. This resolves the “half-finished” objection.
Run a working session with the team and product stakeholders to explain this distinction. Agree that “delivering incrementally” does not mean “exposing incomplete features to users.”
Step 2: Practice decomposing a current feature into vertical slices
Take a feature currently in planning and decompose it into the smallest possible deliverable slices:
- Identify the end state: what does the fully-delivered feature look like?
- Work backward: what is the smallest possible version of this feature that provides any value at all? This is the first slice.
- What addition to that smallest version provides the next unit of value? This is the second slice.
- Continue until the full feature is covered.
A vertical slice cuts through all layers of the stack: it includes backend, API, UI, and tests for one small piece of end-to-end functionality. It is the opposite of “first we build all the backend, then all the frontend.” Each slice is deployable independently.
Step 3: Implement a feature flag for the current feature
For the feature being piloted, add a feature flag:
- Add a configuration-based feature flag that defaults to off.
- Gate the feature’s entry points behind the flag in the codebase.
- Begin merging incremental work to the main branch behind the flag.
- The feature is invisible in production until the flag is enabled, even as components are deployed.
This allows the team to merge small, reviewable changes to main continuously while maintaining the product constraint that the feature is not user-visible until complete.
Step 4: Set a maximum story size
Define a maximum size for individual work items that the team will carry at any one time:
- A story should be completable within one or two days, not one or two weeks.
- A story should result in a pull request that a reviewer can meaningfully review in under an hour - typically under 400 lines of net new code.
- A story should be mergeable to main independently without requiring other stories to ship first (with the feature flag pattern enabling this for user-visible work).
The team will initially find it uncomfortable to decompose work to this granularity. Run decomposition workshops using the feature in Step 2 as practice material.
Step 5: Change the definition of “done” for a story
Redefine “done” to require deployment, not just code completion. A story is done when:
- The code is merged to main.
- The CI pipeline passes.
- The change is deployed to staging (or production behind a flag).
“Code complete” in a branch is not done. “In review” is not done. “Waiting for merge” is not done. This definition forces small batches because a story that cannot be merged to main is not done, and a story that cannot be merged to main is probably too large.
Step 6: Retrospect on the first feature delivered incrementally
After completing the pilot feature using incremental delivery, hold a focused retrospective:
- How did the review experience compare to large feature reviews?
- Were integration problems caught earlier?
- Did the timeline feel more predictable?
- What decomposition decisions could have been better?
Use the retrospective findings to refine the decomposition practice and the maximum story size guideline.
| Objection | Response |
|---|---|
| “Our features are too complex to decompose into small pieces” | Every feature that has ever been built was built one small piece at a time - the question is whether those pieces are integrated continuously or accumulated in a branch. Take your current most complex feature and run the vertical slice decomposition from Step 2 on it - most teams find at least three independently deliverable slices within the first hour. |
| “Product management defines features, not the team - we cannot change the batch size” | Product management defines what users see, not how code is organized or deployed. Introduce the deployment-vs-release distinction in your next sprint planning. Product management can still plan user-visible features of any size; the team controls how those features are delivered underneath. |
| “Our system requires all components to be updated together” | This is an architectural constraint worth addressing. Backward-compatible changes, API versioning, and the expand-contract pattern allow components to be updated independently. Pick one tightly coupled interface, apply the expand-contract pattern this sprint, and measure whether the next change to that interface requires coordinated deployment. |
| “Code review takes the same amount of time regardless of batch size” | This is not supported by evidence. Review quality and thoroughness decrease sharply with change size. Track actual review time and defect escape rate for your next five large reviews versus your next five small ones - the data will show the difference. |
Measuring Progress
| Metric | What to look for |
|---|---|
| Work in progress | Should decrease as stories are smaller and move through the system faster |
| Development cycle time | Should decrease as features are broken into deliverable slices |
| Integration frequency | Should increase as developers merge to main more often |
| Average pull request size (lines changed) | Should decrease toward a target of under 400 net lines |
| Lead time | Should decrease as features in flight are smaller and complete faster |
| Production incidents per deployment | Should decrease as smaller deployments carry less risk |
Related Content
- Work Decomposition - The practice of breaking large features into small, deliverable slices
- Feature Flags - The mechanism that enables incremental delivery of user-invisible work
- Small Batches - The principle that small changes are safer and faster than large ones
- Monolithic Work Items - A closely related anti-pattern at the story level
- Horizontal Slicing - The anti-pattern of building all the backend before any frontend
6 - Undone Work
Category: Team Workflow | Quality Impact: High
What This Looks Like
A developer moves a story to “Done.” The code is merged. The pull request is closed. But the feature is not actually in production. It is waiting for a downstream team to validate. Or it is waiting for a manual deployment. Or it is waiting for a QA sign-off that happens next week. The board says “Done.” The software says otherwise.
Common variations:
- The external validation queue. The team’s definition of done ends at “code merged to main.” A separate team (QA, data validation, security review) must approve before the change reaches production. Stories sit in a hidden queue between “developer done” and “actually done” with no visibility on the board.
- The merge-without-testing pattern. Code merges to the main branch before all testing is complete. The team considers the story done when the PR merges, but integration tests, end-to-end tests, or manual verification happen later (or never).
- The deployment gap. The code is merged and tested but not deployed. Deployment happens on a schedule (weekly, monthly) or requires a separate team to execute. The feature is “done” in the codebase but does not exist for users.
- The silent handoff. The story moves to done, but the developer quietly tells another team member, “Can you check this in staging when you get a chance?” The remaining work is informal, untracked, and invisible.
The telltale sign: the team’s velocity (stories closed per sprint) looks healthy, but the number of features actually reaching users is much lower.
Why This Is a Problem
Undone work creates a gap between what the team reports and what the team has actually delivered. This gap hides risk, delays feedback, and erodes trust in the team’s metrics.
It reduces quality
When the definition of done does not include validation and deployment, those steps are treated as afterthoughts. Testing that happens days after the code was written is less effective because the developer’s context has faded. Validation by an external team that did not participate in the development catches surface issues but misses the subtle defects that only someone with full context would spot.
When done means “in production and verified,” the team builds validation into their workflow rather than deferring it. Quality checks happen while context is fresh, and the team owns the full outcome.
It increases rework
The longer the gap between “developer done” and “actually done,” the more risk accumulates. A story that sits in a validation queue for a week may conflict with other changes merged in the meantime. When the validation team finally tests it, they find issues that require the developer to context-switch back to work they finished days ago.
If the validation fails, the rework is more expensive because the developer has moved on. They must reload the mental model, re-read the code, and understand what changed in the codebase since they last touched it.
It makes delivery timelines unpredictable
The team reports velocity based on stories they marked as done. But the actual delivery to users lags behind because of the hidden validation and deployment queues. Leadership sees healthy velocity and expects features to be available. When they discover the gap, trust erodes.
The hidden queue also makes cycle time measurements unreliable. The team measures from “started” to “moved to done” but ignores the days or weeks the story spends in validation or waiting for deployment. True cycle time (from start to production) is much longer than reported.
Impact on continuous delivery
CD requires that every change the team completes is genuinely deployable. Undone work breaks this by creating a backlog of changes that are “finished” but not deployed. The pipeline may be technically capable of deploying at any time, but the changes in it have not been validated. The team cannot confidently deploy because they do not know if the “done” code actually works.
CD also requires that done means done. If the team’s definition of done does not include deployment and verification, the team is practicing continuous integration at best, not continuous delivery.
How to Fix It
Step 1: Define done to include production
Write a definition of done that ends with the change running in production and verified. Include every step: code review, all testing (automated and any required manual verification), deployment, and post-deploy health check. If a step is not complete, the story is not done.
Step 2: Make the hidden queues visible
Add columns to the board for every step between “developer done” and “in production.” If there is an external validation queue, it gets a column. If there is a deployment wait, it gets a column. Make the work-in-progress in these hidden stages visible so the team can see where work is actually stuck.
Step 3: Pull validation into the team
If external validation is a bottleneck, bring the validators onto the team or teach the team to do the validation themselves. The goal is to eliminate the handoff. When the developer who wrote the code also validates it (or pairs with someone who can), the feedback loop is immediate and the hidden queue disappears.
If the external team cannot be embedded, negotiate a service-level agreement for validation turnaround and add the expected wait time to the team’s planning. Do not mark stories done until validation is complete.
Step 4: Automate the remaining steps
Every manual step between “code merged” and “in production” is a candidate for automation. Automated testing in the pipeline replaces manual QA sign-off. Automated deployment replaces waiting for a deployment window. Automated health checks replace manual post-deploy verification.
Each step that is automated eliminates a hidden queue and brings “developer done” closer to “actually done.”
| Objection | Response |
|---|---|
| “We can’t deploy until the validation team approves” | Then the story is not done until they approve. Include their approval time in your cycle time measurement and your sprint planning. If the wait is unacceptable, work with the validation team to reduce it or automate it. |
| “Our velocity will drop if we include deployment in done” | Your velocity has been inflated by excluding deployment. The real throughput (features reaching users) has always been lower. Honest velocity enables honest planning. |
| “The deployment schedule is outside our control” | Measure the wait time and make it visible. If a story waits five days for deployment after the code is ready, that is five days of lead time the team is absorbing silently. Making it visible creates pressure to fix the process. |
Measuring Progress
| Metric | What to look for |
|---|---|
| Gap between “developer done” and “in production” | Should decrease toward zero |
| Stories in hidden queues (validation, deployment) | Should decrease as queues are eliminated or automated |
| Lead time | Should decrease as the full path from commit to production shortens |
| Development cycle time | Should become more accurate as it measures the real end-to-end time |
Related Content
- Monolithic Work Items - Large items are more likely to have undone work because they take longer to validate
- Manual Deployments - Manual deployment processes create the deployment gap
- Manual Regression Testing Gates - Manual testing gates create the validation queue
- Working Agreements - The definition of done is a working agreement the team owns
7 - Push-Based Work Assignment
Category: Team Workflow | Quality Impact: High
What This Looks Like
A manager, tech lead, or project manager decides who works on what. Assignments happen during sprint planning, in one-on-ones, or through tickets pre-assigned before the sprint starts. Each team member has “their” stories for the sprint. The assignment is rarely questioned.
Common variations:
- Assignment by specialty. “You’re the database person, so you take the database stories.” Work is routed by perceived expertise rather than team priority.
- Assignment by availability. A manager looks at who is “free” and assigns the next item from the backlog, regardless of what the team needs finished.
- Assignment by seniority. Senior developers get the interesting or high-priority work. Junior developers get what’s left.
- Pre-loaded sprints. Every team member enters the sprint with their work already assigned. The sprint board is fully allocated on day one.
The telltale sign: if you ask a developer “what should you work on next?” and the answer is “I don’t know, I need to ask my manager,” work is being pushed.
Why This Is a Problem
Push-based assignment is one of the most quietly destructive practices a team can have. It undermines nearly every CD practice by breaking the connection between the team and the flow of work. Each of its effects compounds the others.
It reduces quality
Push assignment makes code review feel like a distraction from “my stories.” When every developer has their own assigned work, reviewing someone else’s pull request is time spent not making progress on your own assignment. Reviews sit for hours or days because the reviewer is busy with their own work. The same dynamic discourages pairing: spending an hour helping a colleague means falling behind on your own assignments, so developers don’t offer and don’t ask.
This means fewer eyes on every change. Defects that a second person would catch in minutes survive into production. Knowledge stays siloed because there is no reason to look at code outside your assignment. The team’s collective understanding of the codebase narrows over time.
In a pull system, reviewing code and unblocking teammates are the highest-priority activities because finishing the team’s work is everyone’s work. Reviews happen quickly because they are not competing with “my stories” - they are the work. Pairing happens naturally because anyone might pick up any story, and asking for help is how the team moves its highest-priority item forward.
It increases rework
Push assignment routes work by specialty: “You’re the database person, so you take the database stories.” This creates knowledge silos where only one person understands a part of the system. When the same person always works on the same area, mistakes go unreviewed by anyone with a fresh perspective. Assumptions go unchallenged because the reviewer lacks context to question them.
Misinterpretation of requirements also increases. The assigned developer may not have context on why a story is high priority or what business outcome it serves - they received it as an assignment, not as a problem to solve. When the result doesn’t match what was needed, the story comes back for rework.
In a pull system, anyone might pick up any story, so knowledge spreads across the team. Fresh eyes catch assumptions that a domain expert would miss. Developers who pull a story engage with its priority and purpose because they chose it from the top of the backlog. Rework drops because more perspectives are involved earlier.
It makes delivery timelines unpredictable
Push assignment optimizes for utilization - keeping everyone busy - not for flow - getting things done. Every developer has their own assigned work, so team WIP is the sum of all individual assignments. There is no mechanism to say “we have too much in progress, let’s finish something first.” WIP limits become meaningless when the person assigning work doesn’t see the full picture.
Bottlenecks are invisible because the manager assigns around them instead of surfacing them. If one area of the system is a constraint, the assigner may not notice because they are looking at people, not flow. In a pull system, the bottleneck becomes obvious: work piles up in one column and nobody pulls it because the downstream step is full.
Workloads are uneven because managers cannot perfectly predict how long work will take. Some people finish early and sit idle or start low-priority work, while others are overloaded. Feedback loops are slow because the order of work is decided at sprint planning; if priorities change mid-sprint, the manager must reassign. Throughput becomes erratic - some sprints deliver a lot, others very little, with no clear pattern.
In a pull system, workloads self-balance: whoever finishes first pulls the next item. Bottlenecks are visible. WIP limits actually work because the team collectively decides what to start. The team automatically adapts to priority changes because the next person who finishes simply pulls whatever is now most important.
It removes team ownership
Pull systems create shared ownership of the backlog. The team collectively cares about the priority order because they are collectively responsible for finishing work. Push systems create individual ownership: “that’s not my story.” When a developer finishes their assigned work, they wait for more assignments instead of looking at what the team needs.
This extends beyond task selection. In a push system, developers stop thinking about the team’s goals and start thinking about their own assignments. Swarming - multiple people collaborating to finish the highest-priority item - is impossible when everyone “has their own stuff.” If a story is stuck, the assigned developer struggles alone while teammates work on their own assignments.
The unavailability problem makes this worse. When each person works in isolation on “their” stories, the rest of the team has no context on what that person is doing, how the work is structured, or what decisions have been made. If the assigned person is out sick, on vacation, or leaves the company, nobody can pick up where they left off. The work either stalls until that person returns or another developer starts over - rereading requirements, reverse-engineering half-finished code, and rediscovering decisions that were never shared. In a pull system, the team maintains context on in-progress work because anyone might have pulled it, standups focus on the work rather than individual status, and pairing spreads knowledge continuously. When someone is unavailable, the next person simply picks up the item with enough shared context to continue.
Impact on continuous delivery
Continuous delivery depends on a steady, predictable flow of small changes through the pipeline. Push-based assignment produces the opposite: batch-based assignment at sprint planning, uneven bursts of activity as different developers finish at different times, blocked work sitting idle because the assigned person is busy with something else, and no team-level mechanism for optimizing throughput. You cannot build a continuous flow of work when the assignment model is batch-based and individually scoped.
How to Fix It
Step 1: Order the backlog by priority
Before switching to a pull model, the backlog must have a clear priority order. Without it, developers will not know what to pull next.
- Work with the product owner to stack-rank the backlog. Every item has a unique position - no tied priorities.
- Make the priority visible. The top of the board or backlog is the most important item. There is no ambiguity.
- Agree as a team: when you need work, you pull from the top.
Step 2: Stop pre-assigning work in sprint planning
Change the sprint planning conversation. Instead of “who takes this story,” the team:
- Pulls items from the top of the prioritized backlog into the sprint.
- Discusses each item enough for anyone on the team to start it.
- Leaves all items unassigned.
The sprint begins with a list of prioritized work and no assignments. This will feel uncomfortable for the first sprint.
Step 3: Pull work daily
At the daily standup (or anytime during the day), a developer who needs work:
- Looks at the sprint board.
- Checks if any in-progress item needs help (swarm first, pull second).
- If nothing needs help and the WIP limit allows, pulls the top unassigned item and assigns themselves.
The developer picks up the highest-priority available item, not the item that matches their specialty. This is intentional - it spreads knowledge, reduces bus factor, and keeps the team focused on priority rather than comfort.
Step 4: Address the discomfort (Weeks 3-4)
Expect these objections and plan for them:
| Objection | Response |
|---|---|
| “But only Sarah knows the payment system” | That is a knowledge silo and a risk. Pairing Sarah with someone else on payment stories fixes the silo while delivering the work. |
| “I assigned work because nobody was pulling it” | If nobody pulls high-priority work, that is a signal: either the team doesn’t understand the priority, the item is poorly defined, or there is a skill gap. Assignment hides the signal instead of addressing it. |
| “Some developers are faster - I need to assign strategically” | Pull systems self-balance. Faster developers pull more items. Slower developers finish fewer but are never overloaded. The team throughput optimizes naturally. |
| “Management expects me to know who’s working on what” | The board shows who is working on what in real time. Pull systems provide more visibility than pre-assignment because assignments are always current, not a stale plan from sprint planning. |
Step 5: Combine with WIP limits
Pull-based work and WIP limits reinforce each other:
- WIP limits prevent the team from pulling too much work at once.
- Pull-based assignment ensures that when someone finishes, they pull the next priority - not whatever the manager thinks of next.
- Together, they create a system where work flows continuously from backlog to done.
See Limiting WIP for how to set and enforce WIP limits.
What managers do instead
Moving to a pull model does not eliminate the need for leadership. It changes the focus:
| Push model (before) | Pull model (after) |
|---|---|
| Decide who works on what | Ensure the backlog is prioritized and refined |
| Balance workloads manually | Coach the team on swarming and collaboration |
| Track individual assignments | Track flow metrics (cycle time, WIP, throughput) |
| Reassign work when priorities change | Update backlog priority and let the team adapt |
| Manage individual utilization | Remove systemic blockers the team cannot resolve |
Measuring Progress
| Metric | What to look for |
|---|---|
| Percentage of stories pre-assigned at sprint start | Should drop to near zero |
| Work in progress | Should decrease as team focuses on finishing |
| Development cycle time | Should decrease as swarming increases |
| Stories completed per sprint | Should stabilize or increase despite less “busyness” |
| Rework rate | Stories returned for rework or reopened after completion - should decrease |
| Knowledge distribution | Track who works on which parts of the system - should broaden over time |
Related Content
- Everything Started, Nothing Finished - High WIP caused by individual assignment queues
- Pull Requests Sit for Days Waiting for Review - Reviews deprioritized when everyone has their own assigned work
- Uneven Workloads - Imbalance that self-corrects in a pull system
- Blocked Work Sits Idle - Blockers that persist because nobody feels authorized to pick up someone else’s story
- Completed Work Misses the Intent - Rework from developers receiving tickets without business context
- Limiting WIP - Pull-based work and WIP limits are complementary practices
- Work Decomposition - Pull works best when items are small and well-defined
- Working Agreements - The team’s agreement to pull, not push, should be explicit