Skip to content
All posts

Decoupling deploy from release: moving a team off long-lived branches

Four engineers, four repositories, and a branch model where nothing merged until a feature was finished and tested. What changed when we put flags first and made the merge boring.

Case study
5 min read
  • Trunk-based development
  • Feature flags
  • LaunchDarkly
  • CI/CD
  • Engineering leadership

A branch lived until its feature was complete and tested. That sounds responsible until you follow it to its conclusion: nothing reached users in between, so we released once a sprint — sometimes once every two. Pull requests piled up behind each other. Cycle time was measured in sprints because the branch model made it a sprint-shaped quantity.

Four engineers, four repositories. We put feature flags in first, changed the branch policy second, and stopped treating the merge as the moment of truth. Cycle time dropped ~30%.

Key Takeaways

  • Flags first, branch policy second. Merging unfinished work into trunk is only safe once you can turn it off, so the kill switch has to exist before the policy changes.
  • Releases went from once a sprint or two to whenever a chunk was finished — sometimes on developer confidence alone, sometimes after QA.
  • Cycle time fell ~30%, measured from Jira over a two-week window. That is a short sample across a four-person team, not a controlled study.
  • A flag has to be someone's job to delete. In Kubernetes, half of all feature gates stay active for at least 734 days — roughly two years (Tërnava 2026, a preprint).

What long-lived branches actually cost

The pile-up was the visible symptom. Each branch carried a whole finished feature, so every review was large, and reviews queued behind each other. The quieter symptom: we couldn't A/B test. An experiment needs both paths in production and a runtime choice between them, and a model that merges only finished work has no mechanism for that.

DORA's definition of trunk-based development is unusually concrete: fewer than three active branches, merged to trunk at least daily, branches living hours rather than weeks. It is a threshold, not a curve — I could find no study mapping branch age in days onto lead time as a continuous relationship. Past roughly a day, you aren't continuously integrating; you're merging eventually.

The position is old. Thoughtworks put feature branching on Hold in 2011 and never moved it. Newer is the failure mode: reviewing the April 2024 Technology Radar, CTO Emerita Rebecca Parsons described teams showing "a desire to cling to the pull request model, while doing everything possible to make it look more like trunk-based development, without actually giving up the pull request".

Deploy is not release

Martin Fowler's taxonomy of toggles does the work here. A release toggle hides work in progress; an experiment toggle splits traffic between paths. We needed both — the A/B testing requirement and the branch-model problem had the same solution, and neither was going to be fixed by getting better at merging.

Once unfinished work can sit on trunk turned off, the reason to hold a branch open disappears. You merge because it compiles and passes tests, not because the feature is done — and releasing becomes a separate decision, made by whoever owns the outcome, not the branch.

Flags first

The order mattered more than the timeline. Asking a team to merge incomplete work into trunk before there is a way to switch it off is asking them to take a risk with no instrument. Flags went in first, with four conventions:

  • Names carry meaning, and every flag carries a description. A key you've to go ask someone about is a key nobody will dare delete.
  • A developer owns the flag. Ownership is technical, and it is individual.
  • The product owner runs the rollout. Whoever owns the outcome decides when it reaches users. That is the whole point of separating the two events.
  • The developer who added the flag removes it, with the lifetime scoped to the rollout. It is the same rule as keeping a fallback during a migration: set the deletion date when you create the thing.

Branch policy changed after that, and pull requests got smaller and more frequent as a direct consequence — a chunk you can flag off is a chunk you can merge today.

The flag debt you're signing up for

This is the part the vendor blogs skip. Every flag is a branch in the code that outlives the branch in git, and the published evidence on how long is worth internalising before you start.

Median feature-flag lifespan in two large open-source projects Median survival time of feature toggles: 734 days in Kubernetes and 185 days in GitLab, against a research benchmark of 49 weeks, roughly 343 days, within which 75 percent of toggles in a study of Python projects had been removed. Median feature-toggle survival time (days) Kubernetes 734 GitLab 185 343 days — the 49-week mark Survival times: Tërnava 2026, arXiv preprint — single author, not peer reviewed. Benchmark: Empirical Software Engineering, 2021.
Kubernetes carries 155 active feature gates, GitLab 403. Both projects remove flags; both remove them slower than they add them. Figures from Tërnava 2026.

A 2021 study in Empirical Software Engineering found 75% of toggle components gone within 49 weeks across twelve Python projects. The pessimistic reading is the other 25%: practitioners surveyed in the same work said they intended to remove toggles once a feature stabilised, and often didn't. At scale it becomes a tooling problem — Uber built Piranha to generate cleanup diffs for 1,381 stale flags, 17% of its total.

Four repositories isn't Kubernetes. But the convention that a developer removes their own flag exists precisely because that curve is the default, and defaults win unless someone is accountable.

Was it worth it

Yes, with the numbers stated honestly.

BeforeAfter
Release cadenceOnce a sprint, sometimes twoWhen a chunk is finished
Merge triggerFeature complete and testedCompiles and passes tests
Cycle time—~30% lower
Rollback pathRevert a finished-feature mergeFlip a flag

The rollback change is the one I'd emphasise. Flags have served as a kill switch for features found to have critical issues in production — turning a bad release into a toggle rather than a revert of a large, already-merged feature. That is what has actually paid for the flag infrastructure.

The 30% deserves its caveat: it comes from Jira over a two-week window, on a four-person team across four repositories. A real measurement of a real change, but a short sample is a short sample, and I wouldn't extrapolate it to a different team shape.

What I'd tell someone starting: the technical work is a flag SDK and a branch policy, and it is the small half. The other half is convincing people that merging unfinished work into trunk is safer than holding it private — a claim about your test suite and your rollback path, not about git.