Modern XP: The Delivery Loop
If your branch lives for more than a day, you’re not doing continuous integration. You’re doing periodic integration. The distinction matters more than most teams realize.
This is the third of four posts on XP’s reinforcing practice loops. The code quality loop covered TDD, refactoring, and simple design. The knowledge sharing loop covered pair programming and collective ownership. This post covers the two practices that turn working code into delivered value: continuous integration and small releases.
Continuous integration: what it actually means
When Kent Beck described continuous integration, he meant something specific: every developer integrates their work into a shared mainline at least once a day. Not once a sprint. Not when the feature is “done.” Every day.
The term has been co-opted to mean “we have a CI server.” Jenkins runs. GitHub Actions fire. Builds happen. But if developers work on feature branches for days or weeks before merging, the CI server is testing isolated work, not integrated work. The “continuous” part is missing.
Real continuous integration has a few properties:
Integration happens at least daily. Every developer merges their work into the mainline (trunk, main, whatever you call it) at least once per day. This means working in small increments that can be integrated without breaking the build.
The build is always green. A broken build on the mainline is an emergency, not a nuisance. The team stops and fixes it immediately. This requires discipline: you don’t merge code that breaks tests, and you don’t leave the build broken while you “figure it out.”
Everyone works against the same codebase. There are no long-lived branches diverging from each other. Merge conflicts are small and frequent, not large and terrifying. The cost of integration is paid continuously in small amounts rather than in painful bursts.
Why branches undermine CI
Feature branches feel safe. You work in isolation, nobody’s changes interfere with yours, and you merge when you’re ready. The problem is what “ready” means in practice.
A branch that lives for three days has three days of drift from the mainline. Other developers have changed the code you’re building on. Your mental model of the codebase is three days stale. When you merge, you’re integrating three days of isolated work against three days of changes from everyone else.
A branch that lives for two weeks is worse. The merge is larger. The conflicts are more complex. The risk of introducing a bug during the merge is higher. And the feedback on whether your work integrates correctly is delayed by two weeks.
This is the integration problem that CI was designed to solve. Not by automating builds (though that helps), but by integrating frequently enough that the cost of each integration is trivially small.
Trunk-based development
The practice that supports real CI is trunk-based development: everyone commits to the mainline (or to short-lived branches that merge within hours, not days).
This requires a shift in how teams work:
Work in small slices. A feature that takes a week doesn’t mean a week-long branch. It means multiple daily commits that incrementally build toward the feature. Each commit is safe to integrate because it doesn’t break existing behavior.
Use feature flags for incomplete work. If a feature isn’t ready for users, hide it behind a flag. The code is integrated and tested on the mainline, but the behavior isn’t visible until the flag is enabled. This separates deployment from release.
Keep the build fast. If the build takes 30 minutes, developers won’t integrate multiple times a day. Fast builds (under 10 minutes, ideally under 5) make frequent integration practical.
This is harder than working on long-lived branches. It requires discipline, small steps, and a test suite you trust. It’s also where the dependencies on the other XP practices become clear.
Small releases: ship early, ship often
Small releases means putting working software in front of users frequently. Not once a quarter. Not once a month. As often as the business can absorb feedback.
In 1999, small releases might have meant every few weeks. With modern deployment infrastructure, it can mean multiple times a day. The principle is the same: reduce the distance between writing code and learning whether it was the right code to write.
Why small releases matter
Smaller blast radius. A release with three changes is easier to debug than a release with thirty. When something breaks, the cause is obvious. Rollback is straightforward. The risk per release approaches zero.
Faster feedback. Users interact with real software, not mockups or specifications. Problems are discovered when they’re cheap to fix, not after months of additional work has been built on top of flawed assumptions.
Reduced inventory. Unreleased code is inventory. It has cost you time and money to build, but it’s delivering zero value until it reaches users. Large batches of unreleased code represent large amounts of unrecovered investment. Small releases minimize this inventory.
Lower stress. Release day at many organizations is an event: a deployment window, a war room, people on standby. This is a symptom of large, infrequent releases. When releases are small and frequent, each one is routine. No war room. No drama. Just another deployment.
The dependency on CI
Small releases require CI. You can’t ship frequently if the mainline is broken. You can’t ship confidently if the test suite doesn’t cover the behavior you’re releasing. You can’t ship small increments if developers are working on two-week branches that can only be released as a batch.
CI keeps the mainline in a releasable state. Tests verify that the code does what it should. Trunk-based development ensures that what’s on the mainline is the latest integrated work from the entire team. These conditions make small releases possible.
Without CI, small releases are risky. You don’t know if the code works. With CI but without small releases, you’re testing integration but not delivery. The build is green, but the software is sitting in a queue waiting for “release day.”
The delivery loop in action
Here’s what this loop looks like when both practices are working together.
A developer writes a small change. It’s backed by tests (from the code quality loop). They merge it to the mainline. The CI pipeline runs in a few minutes. Tests pass. The change is deployed to a staging environment automatically.
After a few such changes accumulate (maybe over hours, maybe over a day), the team releases to production. Maybe this is automated too: every green build deploys. Maybe there’s a manual step. Either way, the batch size is small. The risk is low. The feedback is fast.
When a problem is discovered in production, the team knows which small change caused it. The fix is straightforward: either fix the issue (which is small) or revert the change (which is also small). There’s no archaeological expedition through a month of changes trying to find the culprit.
Compare this to the typical alternative: developers work on branches for a week, merge them all during a “code freeze,” spend a day resolving merge conflicts, deploy the combined batch, and then debug issues that could have been caused by any of twenty changes.
What breaks when you remove a side
CI without small releases. The mainline is always green. Tests pass. Integration is smooth. But the software ships once a month in a large batch. You’ve solved the integration problem but not the delivery problem. Feedback is still slow. Inventory still accumulates. Release day is still stressful because the batch is large even though each individual integration was small.
Small releases without CI. You try to ship frequently, but the mainline is unreliable. Developers work on branches and merge sporadically. The code that reaches production hasn’t been tested together. Releases break. The team loses confidence and ships less frequently, which makes each release larger, which makes each release riskier. A downward spiral.
Modern tooling has removed the hard parts
In 1999, continuous integration meant a dedicated machine running a build script. Deployment meant someone copying files to a server. The practices were sound but the execution was manual and error-prone.
Today:
CI/CD pipelines are a commodity. GitHub Actions, GitLab CI, Azure DevOps, and dozens of other tools make automated build-test-deploy workflows straightforward to set up.
Containerization makes environments reproducible. The build that passes in CI behaves the same in production.
Feature flags are mature. LaunchDarkly, Split, and open-source alternatives make it routine to separate deployment from release.
Infrastructure as code means spinning up environments is automated. You can test in production-like conditions without manual setup.
Observability tools give you fast feedback after release. If something goes wrong, you know within minutes, not days.
The tooling bottleneck is gone. What remains is discipline: working in small increments, merging frequently, keeping the build green, and releasing often. These are team habits, not technology problems.
Getting started
Measure your current cycle time. How long does it take from a developer’s first commit on a feature to that feature reaching production? If it’s weeks, you have room to improve.
Shorten branch lifetimes. If your branches live for a week, try to get them to three days. Then two. Then one. Each reduction surfaces integration problems earlier and reduces merge complexity.
Automate the pipeline. If deployment requires manual steps, automate them. Every manual step is a reason to deploy less frequently.
Make the build fast. If your CI pipeline takes 30 minutes, developers will avoid running it. Invest in parallelization, test optimization, and infrastructure. A 5-minute build changes behavior.
Release smaller batches. If you release monthly, try biweekly. Then weekly. Then daily. Each step reduces risk and increases feedback speed.
The delivery loop gets working code to users. But shipping code isn’t valuable if you’re shipping the wrong thing. The next post covers the feedback loop: the planning game and customer collaboration. These practices ensure that what you build is what users need.
Found this useful?
If this post helped you, consider buying me a coffee.
Comments