This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Team Workflow

Anti-patterns in how teams assign, coordinate, and manage the flow of work.

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.

1 - Horizontal Slicing

Work is organized by technical layer (“build the API,” “update the schema”) rather than by independently deliverable behavior. Nothing ships until all the pieces are assembled.

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):

  1. Add new columns to the orders table
  2. Build the discount calculation endpoint
  3. Update the order summary UI component
  4. Integration testing across services

After (vertical, within team’s domain):

  1. Apply a percentage discount to a single-item order (schema + logic + contract)
  2. Apply a percentage discount to a multi-item order
  3. Reject an expired discount code with a clear error response
  4. 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.

ObjectionResponse
“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

MetricWhat to look for
Percentage of work items that are independently deployableShould increase toward 100%
Time from feature start to first production deployShould decrease as the first vertical slice ships early
Cross-team deployment dependencies per featureShould decrease toward zero
Development cycle timeShould decrease as items no longer wait for other layers or teams
Integration frequencyShould increase as deployable slices are completed and merged daily
  • 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

Work items go from product request to developer without being broken into smaller pieces. Items are as large as the feature they describe.

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:

  1. Product owner presents the feature or outcome.
  2. Team writes acceptance criteria in Given-When-Then format.
  3. If the item has more than three to five criteria, split it.
  4. Each resulting item is estimated. Any item over two days is split again.
  5. 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:

Acceptance criteria as Gherkin scenarios for independent delivery
Scenario: Apply percentage discount
  Given a cart with items totaling $100
  When I apply a 10% discount code
  Then the cart total should be $90

Scenario: Reject expired discount code
  Given a cart with items totaling $100
  When I apply an expired discount code
  Then the cart total should remain $100

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.

ObjectionResponse
“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

MetricWhat to look for
Item cycle timeShould be two days or less from start to trunk
Development cycle timeShould decrease as items get smaller
Items completed per weekShould increase
Integration frequencyShould increase as developers integrate daily

3 - Unbounded WIP

The team has no constraint on how many items can be in progress at once. Work accumulates because there is nothing to stop starting and force finishing.

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.

ObjectionResponse
“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

MetricWhat to look for
Work in progressShould stay at or below the team’s limit
Development cycle timeShould decrease as WIP drops
Items completed per weekShould stabilize or increase despite starting fewer
Time items spend blockedShould decrease as the team swarms on blockers

4 - Knowledge Silos

Only specific individuals can work on or review certain parts of the codebase. The team’s capacity is constrained by who knows what.

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.

ObjectionResponse
“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

MetricWhat to look for
Knowledge matrix coverageEach subsystem should have at least two developers who can work in it
Review distributionReviews should be spread across the team, not concentrated in one or two people
Bus factor per subsystemShould increase from one to at least two
Blocked time due to unavailable expertShould decrease toward zero

5 - Big-Bang Feature Delivery

Features are designed and built as large monolithic units with no incremental delivery - either the whole feature ships or nothing does.

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:

  1. Identify the end state: what does the fully-delivered feature look like?
  2. Work backward: what is the smallest possible version of this feature that provides any value at all? This is the first slice.
  3. What addition to that smallest version provides the next unit of value? This is the second slice.
  4. 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:

  1. Add a configuration-based feature flag that defaults to off.
  2. Gate the feature’s entry points behind the flag in the codebase.
  3. Begin merging incremental work to the main branch behind the flag.
  4. 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:

  1. The code is merged to main.
  2. The CI pipeline passes.
  3. 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.

ObjectionResponse
“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

MetricWhat to look for
Work in progressShould decrease as stories are smaller and move through the system faster
Development cycle timeShould decrease as features are broken into deliverable slices
Integration frequencyShould 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 timeShould decrease as features in flight are smaller and complete faster
Production incidents per deploymentShould decrease as smaller deployments carry less risk
  • 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

Work is marked complete before it is truly done. Hidden steps remain after the story is closed, including testing, validation, or deployment that someone else must finish.

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.”

ObjectionResponse
“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

MetricWhat 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 timeShould decrease as the full path from commit to production shortens
Development cycle timeShould become more accurate as it measures the real end-to-end time

7 - Push-Based Work Assignment

Work is assigned to individuals by a manager or lead instead of team members pulling the next highest-priority item.

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:

  1. Pulls items from the top of the prioritized backlog into the sprint.
  2. Discusses each item enough for anyone on the team to start it.
  3. 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:

  1. Looks at the sprint board.
  2. Checks if any in-progress item needs help (swarm first, pull second).
  3. 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:

ObjectionResponse
“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 whatEnsure the backlog is prioritized and refined
Balance workloads manuallyCoach the team on swarming and collaboration
Track individual assignmentsTrack flow metrics (cycle time, WIP, throughput)
Reassign work when priorities changeUpdate backlog priority and let the team adapt
Manage individual utilizationRemove systemic blockers the team cannot resolve

Measuring Progress

MetricWhat to look for
Percentage of stories pre-assigned at sprint startShould drop to near zero
Work in progressShould decrease as team focuses on finishing
Development cycle timeShould decrease as swarming increases
Stories completed per sprintShould stabilize or increase despite less “busyness”
Rework rateStories returned for rework or reopened after completion - should decrease
Knowledge distributionTrack who works on which parts of the system - should broaden over time