All articles
Git Branching Workflow Release Management Engineering Management

GitFlow and Branching Strategies: Which Workflow Fits Your Team

Palakorn Voramongkol
April 18, 2026 14 min read

“GitFlow, GitHub Flow, GitLab Flow, and trunk-based development — what each is, when each fits, concrete branch diagrams, the release ceremonies they imply, and how to actually pick one in 2026.”

A git branching strategy is a small decision that compounds into a cultural one. It quietly decides how often you release, how long code sits unmerged, how risky a hotfix is, and whether “long-lived feature branch” is a normal phrase or an alarm bell. It’s also a decision teams make once, then never revisit — even when the team, product, or deployment pace changes out from under it.

This post is the short version of that decision. Four common strategies — GitFlow, GitHub Flow, GitLab Flow, trunk-based development — what each actually is, what it implies, what it costs, and how to pick one now without committing to it forever.

TL;DR

  • GitFlow = versioned releases + long-lived develop + release/hotfix branches. Right for shrink-wrapped software with explicit releases. Overkill for most web services.
  • GitHub Flow = main + short-lived feature branches + PR-triggered CI/CD. Simple, fits 80% of web teams.
  • GitLab Flow = GitHub Flow + per-environment branches (pre-prod, production). Good when deploy ≠ merge.
  • Trunk-based development = everyone commits to main (or main + ≤ 24h feature branches); features hidden behind feature flags. Highest velocity, highest feature-flag discipline.
  • The strategy must match how often you ship. Releasing twice a day on GitFlow is suffering; quarterly point releases on trunk-based is chaos.

Why Branching Strategy Matters

Branches are free. What isn’t free is what each branch implies — the merge it demands, the CI it triggers, the release-notes chapter, the regression surface. A long-lived branch accumulates conflicts exponentially. A “release” branch with no automation costs a whole person’s Thursday. A feature branch that skips develop and lands in main during a release freeze is a production incident waiting for a name.

The strategy is really four questions answered at once:

QuestionPossible answers
Where does main “live”?Always-deployable head (trunk/GitHub Flow) vs an integration branch separate from releases (GitFlow).
How are releases cut?Continuous (every merge) vs explicit version (tag on main) vs release branch promoted over time (GitFlow).
How do hotfixes work?From main/trunk (trunk-based) vs dedicated hotfix branches (GitFlow) vs revert-and-reship (GitHub Flow).
How do we prevent WIP in prod?Short branches + tight PRs (GitHub Flow) vs feature flags (trunk-based) vs release gating (GitFlow).

Every strategy below is a coherent set of answers to those four. The wrong thing is mixing answers — calling your strategy “GitFlow” but shipping out of develop, or “trunk-based” with six-month feature branches.

Strategy 1 — GitFlow

The original (Vincent Driessen, 2010). Aimed at software with explicit numbered releases — think boxed software, mobile apps that go through store review, firmware. Structured, ceremonial, and a little over-engineered for most web services today.

Branches

BranchPurposeLifetime
main (or master)Production. Tagged releases only. Never committed to directly.Permanent.
developIntegration branch. All completed features merge here.Permanent.
feature/*One feature; branches off develop, merges back into develop.Short-to-medium.
release/*Preparing a release; branched off develop, merged into both main and develop.Short (days).
hotfix/*Urgent fix; branched off main, merged into both main and develop.Very short.

Lifecycle

  1. Engineer branches feature/payment-2fa off develop, commits, opens PR.
  2. PR merges back into develop when reviewed.
  3. When enough features accumulate, PM declares “we’ll ship v2.3” — branch release/2.3 off develop.
  4. Only bug fixes go to release/2.3 (no new features). develop is free to receive features for v2.4.
  5. When ready, merge release/2.3main, tag v2.3, and merge back into develop (to pick up bug fixes).
  6. If prod breaks: hotfix/2.3.1 off main → fix → merge to both main (tag v2.3.1) and develop.

Fits when

  • You ship explicit versions (mobile apps, libraries, SDKs, desktop software).
  • Releases are infrequent (every few weeks or slower).
  • You must support multiple versions in parallel (v2.x and v3.x both live).
  • “Release notes” is a real artifact the business cares about.

Doesn’t fit when

  • You ship more than once a day.
  • You have fewer than 5 engineers (the ceremony overhead isn’t worth it).
  • Your product is a web service where every merge goes to production anyway.

The common mistake

Teams adopt GitFlow because they read the canonical blog post, then discover they actually ship continuously. The developrelease/*main dance becomes pure overhead — develop ends up identical to main, and the release branch is a zero-commit ceremony. If that describes you, you want GitHub Flow, not GitFlow.

Strategy 2 — GitHub Flow

Introduced by GitHub alongside Pull Requests. Simple and the default for most web teams. Released as a short guide by Scott Chacon in 2011.

Branches

BranchPurposeLifetime
mainAlways-deployable. Every merge goes to production.Permanent.
feature/* (or just username/feature)One change. Branches off main, merges back via PR.Short (hours to days).

That’s it. No develop, no release/*, no separate hotfix branch — a hotfix is just a small PR with fast review.

Lifecycle

  1. Branch off main.
  2. Commit.
  3. Open PR → CI runs → review → merge.
  4. Merge to main triggers deploy to production (via CI/CD).
  5. Delete branch.

Fits when

  • You deploy continuously (at least daily).
  • Your product is a web service or API where “version” means “current state of prod”.
  • Small-to-mid team (2–30 engineers).
  • Feature flags (or short branches) keep WIP out of production.

Doesn’t fit when

  • You release versioned software that customers install.
  • You have a distinct QA stage that takes days before anything reaches prod.
  • You need to maintain and patch older versions in parallel.

The common mistake

Merging to main but not automating deploy. If main is meant to be always-deployable but in practice it sits a week before being released, you’ve bought the overhead of GitHub Flow without the benefit. Either automate the deploy, or adopt a strategy with an explicit release step.

Strategy 3 — GitLab Flow

GitHub Flow + explicit environment branches. Pragmatic middle ground for teams that want continuous merging but not continuous deployment.

Branches

BranchPurposePromotion rule
mainIntegration. Merged features land here.From PR.
pre-productionAuto-deployed to staging.Fast-forward from main.
productionAuto-deployed to prod.Fast-forward from pre-production (after QA sign-off).

Each environment is a branch that is strictly behind the previous. To promote a change, you merge (or fast-forward) one branch into the next.

Lifecycle

  1. Branch off main, commit, PR, merge to main.
  2. main auto-deploys to dev environment.
  3. When QA is ready: git merge mainpre-production → deploys to staging.
  4. When release manager is ready: git merge pre-productionproduction → deploys to prod.

Fits when

  • You want every change integrated continuously but released on a cadence.
  • You have a staging environment where code needs to “bake” before prod.
  • You have auditors who want to see “what’s in pre-prod” vs “what’s in prod”.

Doesn’t fit when

  • Your deploy is truly continuous; the extra branches just add overhead.
  • You change environments often; each change needs the branch pipeline updated.

Strategy 4 — Trunk-Based Development

Everyone commits to a single branch — main or trunk — directly or via branches that live for less than 24 hours. Incomplete features are hidden behind feature flags. Releases are cut by tagging a commit on main.

This is how Google, Meta, and most very-high-velocity teams work. It demands the highest discipline — but delivers the fastest integration and the shortest merge conflicts.

Branches

BranchPurposeLifetime
main (trunk)The single source of truth. Every commit must pass CI.Permanent.
Optional: short-lived feature branchesFor work > 1 day, merged back fast.≤ 24 hours.
release/*Optional: cut per release for long-term support, but never worked on.As long as the release is supported.

Lifecycle

  1. Start work on main (or branch and merge within 24h).
  2. Hide the unfinished feature behind a flag (if (features.newCheckout)).
  3. Continuously push small commits to main that pass CI.
  4. When feature is done: turn the flag on — either for everyone, or progressively (canary).
  5. Hotfix = revert the bad commit from main and roll forward (not a separate branch).

Fits when

  • High-velocity teams (hundreds of deploys per day).
  • Feature-flag infrastructure is robust.
  • CI is reliable and fast (under 10 min to green).
  • Pair programming or short-turnaround review culture.

Doesn’t fit when

  • You release on a schedule customers install (versioned software).
  • Feature-flag discipline is weak (flags become dead code or bugs leak through).
  • CI is slow or flaky — devs will start bypassing it.

The common mistake

“Trunk-based” is not “no branches” — it’s “no long branches”. Teams read “commit to main” literally, skip review, and flaky code lands in prod. The discipline that makes trunk-based work is short branches + mandatory review + reliable CI. Remove any of those and you have chaos.

Which Strategy Should You Pick?

A decision matrix using the four questions at the top of this post.

Team characteristicGitFlowGitHub FlowGitLab FlowTrunk-based
Ships versioned software✅ Ideal⚠️ Only with release branches
Ships web service continuously✅ Ideal⚠️ OK✅ Ideal
Multiple live versions supported⚠️ Hard
Distinct QA stage before prod⚠️ Needs staging✅ Natural fit⚠️ Via feature flag
Team ≥ 50 engineers⚠️ Heavy✅ (with discipline)
Team ≤ 10 engineers❌ Over-engineered✅ Ideal⚠️ Maybe⚠️ Overhead of flags
Feature flags in placen/aNice to haveNice to have✅ Required
Deploy automation maturen/aRequiredRequiredRequired

The 80% answer in 2026: GitHub Flow. The 15% answer: trunk-based, if the team has the feature-flag discipline. The 4%: GitLab Flow, if you really need environment branches. The 1%: GitFlow, if you ship versioned software that customers install.

Concrete Policies You Still Need, Regardless

Strategy only gets you so far. These policies do the rest of the work.

PolicyWhy it matters
Branch protection on mainNo force-push, no direct commits. PR required.
Required status checksCI must pass; code coverage can’t drop.
Required reviewsAt least one approval; code-owners for sensitive paths.
Conventional commitsfeat:, fix:, chore: — enables automatic changelog + semver bump.
Squash-merge PRsOne commit per feature in main history — easier to revert, read, bisect.
PRs under ~400 linesReviewer attention drops fast past this. Split work into smaller PRs.
Linear history on mainEither squash-merge or rebase-merge. No merge commits from PRs.
Delete branch on mergeKeeps the branch list manageable.

These apply to all four strategies above. They are what makes any strategy actually work.

Feature Flags Are the Missing Piece

If you’ve chosen trunk-based or GitHub Flow with fast deploys, feature flags aren’t optional — they’re the safety net that makes “merge to main” not terrifying.

At minimum you want:

  • Off-by-default boolean flags — new code paths guarded; feature ships only when flag is on.
  • Percentage rollouts — 1% → 10% → 50% → 100%, with ability to roll back fast.
  • Kill switches — dangerous features with a flag you can flip off in 30 seconds.
  • Flag lifecycle discipline — flag created with an owner and a kill date. Expired flags get removed as tech debt.

Tools: LaunchDarkly, Unleash, PostHog, GrowthBook, Flagsmith, or (for small scale) a config table in your own DB. The platform matters less than the discipline.

See the companion post: Feature Flags and Progressive Delivery in Production.

Migrating Between Strategies

You probably won’t pick the perfect strategy first try. Here’s how to move.

GitFlow → GitHub Flow

  1. Merge develop into main; delete develop.
  2. Wire up a CI/CD that deploys on merge to main.
  3. Convert existing feature/* branches — rebase them onto main, merge.
  4. Delete old release/* branches; keep tags on main instead.

The friction is usually cultural, not technical: engineers used to “commit to develop” hesitate to commit to a branch that ships immediately. Feature flags and small PRs solve this.

GitHub Flow → Trunk-Based

  1. Introduce feature flags first (spend a sprint on infrastructure).
  2. Shorten branch lifetimes — target 1 day maximum.
  3. Once branches are genuinely short, let engineers push directly (still via CI) for small changes.

Don’t skip step 1. “Trunk-based without feature flags” is just “shipping WIP”.

GitHub Flow → GitLab Flow

  1. Create pre-production and production branches from main.
  2. Wire deploy pipelines to the two new branches instead of to main.
  3. Adopt the promotion ritual — mainpre-productionproduction, each via fast-forward merge.

Worked Example: What Each Looks Like on the Commit Graph

Imagine the same week of work — two features, one hotfix, one release — rendered in each strategy:

StrategyWhat the graph shows
GitFlowFeature branches off develop, merged back. release/1.2 branch cut, bug fixes on it, merged to main (tag v1.2) and back to develop. Hotfix branch from main, merged to both. Busy.
GitHub FlowSeveral short-lived feature branches off main, all merged back via PR. No release ceremony; main is always deployable. One extra PR for the hotfix. Clean.
GitLab FlowLike GitHub Flow for main. Plus two additional branches (pre-production, production) fast-forwarded from main on a schedule. Three parallel rails.
Trunk-basedMostly a single straight line on main. Optional short twigs for ≤1-day work. Hotfix is just another commit on main, preceded by a revert if needed. Sparsest graph.

The busier the graph, the more ceremony. Pick the least busy graph your release cadence can support.

Closing Checklist

Before committing to a strategy:

  • Match the strategy to your release cadence — continuous deploys ≠ versioned releases.
  • Branch protection on main / production: no force-push, required reviews, required CI.
  • Automation wired: PR merge → CI → (auto)deploy or promotion step.
  • Feature flags if you’re on trunk-based or fast GitHub Flow.
  • Conventional-commit style so changelogs and bumps can be automated.
  • Squash-merge or rebase-merge; no merge commits from PRs into main history.
  • Branch deletion on merge.
  • One documented “how we release” page so new hires don’t have to ask.

Further Reading

  • Vincent Driessen — A successful Git branching model (the original GitFlow post) at nvie.com. Read it even if you won’t use GitFlow — it coined the vocabulary.
  • Scott Chacon — GitHub Flow and the GitHub docs GitHub Flow guide.
  • GitLab Docs — GitLab Flow. The canonical write-up of the environment-branch variant.
  • trunkbaseddevelopment.com — Paul Hammant’s deep-dive site on trunk-based; includes short-lived-branch vs committed-directly patterns.
  • Continuous Delivery — Humble & Farley (2010). Pre-dates most of these posts but the principles underlie all four strategies.

The best strategy is the one your team actually follows. Pick the lightest approach your release cadence can support, agree on it in writing, and revisit it in six months — because if the team has grown, the strategy probably needs to grow with it.

Comments powered by Giscus are not yet configured. Set PUBLIC_GISCUS_REPO_ID and PUBLIC_GISCUS_CATEGORY_ID in apps/web/.env to enable.

PV

Written by Palakorn Voramongkol

Software Engineer Specialist with 20+ years of experience. Writing about architecture, performance, and building production systems.

More about me

Continue Reading