Git gives you branches and merges, but a workflow decides how a team should use them.
This lesson compares three common models:
Tip: Pick the lightest workflow that still fits your release process. More branches are only useful when they solve a real coordination problem.
GitFlow is a structured workflow designed for teams with planned releases.
Its long-lived branches are:
main for production-ready historydevelop for ongoing integrationSupporting branches include:
feature/*release/*hotfix/*A feature usually starts from develop:
git switch develop
git switch -c feature/add-audit-logsWhen the feature is ready, it goes back into develop. Later, the team creates a release branch:
git switch develop
git switch -c release/2.4.0For urgent production issues, a hotfix starts from main:
git switch main
git switch -c hotfix/fix-payment-timeoutThe fix is merged into main and back into develop.
GitFlow works well when:
The trade-off is more branch overhead.
GitHub Flow is a simpler PR-based model built around one main long-lived branch: main.
The usual steps are:
mainmainExample:
git switch main
git pull origin main
git switch -c feature/update-runbook
git add .
git commit -m "docs: update deployment runbook"
git push -u origin feature/update-runbookGitHub Flow is a strong choice when:
main is kept releasableNote: GitHub Flow depends on a healthy
mainbranch. Fast tests and code review are important because work merges there frequently.
Trunk-Based Development centers on one integration branch, usually main or trunk, and very short-lived branches.
Its main ideas are:
Example:
git switch main
git pull origin main
git switch -c feature/add-cache-metric
git commit -am "feat: add cache hit metric behind feature flag"
git push -u origin feature/add-cache-metricBecause teams merge early, unfinished work is often hidden behind flags.
FEATURE_NEW_DASHBOARD=falseFeature flags separate deployment from release. Code can be merged and even deployed without being visible to users yet.
This workflow is often best for teams that:
Its biggest challenge is discipline. Large long-running branches work against the model.
A small team often starts best with GitHub Flow because it is simple.
A larger organization with formal release windows often prefers GitFlow.
A team shipping many times per day often benefits most from Trunk-Based Development or a lightweight GitHub Flow variant, because both keep integration frequent.
| Workflow | Long-Lived Branches | Best For |
|---|---|---|
| GitFlow | main, develop | planned releases, enterprise coordination |
| GitHub Flow | main | PR-based teams, simpler delivery |
| Trunk-Based Development | main or trunk | fast CI/CD and short-lived branches |
GitFlow adds structure for releases and hotfixes, GitHub Flow keeps things simple around main and pull requests, and Trunk-Based Development pushes toward very fast integration with short-lived branches and feature flags. The best workflow is the one that matches your release cadence, team size, and automation maturity.
<MCQExercise
exercise={{
id: 'git-workflows-q1',
title: 'Exercise 1: Recognizing GitFlow',
description: 'Which workflow uses main, develop, feature/*, release/*, and hotfix/* branches as core concepts?',
options: [
{ id: 'a', text: 'GitFlow' },
{ id: 'b', text: 'GitHub Flow' },
{ id: 'c', text: 'Trunk-Based Development' },
{ id: 'd', text: 'Detached HEAD workflow' },
],
correctId: 'a',
explanation: 'GitFlow is the structured branching model built around main and develop, with separate branch types for features, releases, and hotfixes. That branch taxonomy is one of its defining characteristics.',
wrongExplanations: {
b: 'GitHub Flow is simpler and usually centers on main plus short-lived feature branches and pull requests.',
c: 'Trunk-Based Development focuses on one primary branch and very short-lived branches rather than multiple long-lived branch types.',
d: 'Detached HEAD is a Git state, not a collaboration workflow model.',
},
}}
/>
<MCQExercise exercise={{ id: 'git-workflows-q2', title: 'Exercise 2: Matching a workflow to frequent deployment', description: 'A team deploys many times per day and wants small, fast integrations with feature flags. Which workflow is the best match?', options: [ { id: 'a', text: 'GitFlow, because long-lived release branches speed up continuous delivery' }, { id: 'b', text: 'Trunk-Based Development, because it emphasizes short-lived branches and continuous integration' }, { id: 'c', text: 'GitHub Flow, because it requires no testing at all' }, { id: 'd', text: 'Any workflow, because branch strategy has no effect on delivery speed' }, ], correctId: 'b', explanation: 'The lesson connected Trunk-Based Development with CI/CD-heavy teams that integrate small changes quickly and often use feature flags. That makes it the strongest fit for very frequent deployment.', wrongExplanations: { a: 'GitFlow can work, but its extra release and integration branches usually add more ceremony than fast-delivery teams want.', c: 'GitHub Flow still depends on testing and healthy review practices. It does not remove the need for validation.', d: 'Branching strategy affects merge frequency, feedback speed, and operational flow.', }, }} />
<MCQExercise exercise={{ id: 'git-workflows-q3', title: 'Exercise 3: Choosing for a small team', description: 'A small product team wants one main branch, short-lived feature branches, and pull-request-based review. Which workflow should they start with?', options: [ { id: 'a', text: 'GitFlow' }, { id: 'b', text: 'Submodule workflow' }, { id: 'c', text: 'GitHub Flow' }, { id: 'd', text: 'Vendor branch workflow' }, ], correctId: 'c', explanation: 'GitHub Flow maps directly to a main branch plus short-lived feature branches reviewed through pull requests. That makes it a strong starting point for small teams.', wrongExplanations: { a: 'GitFlow adds more branch types and is usually heavier than a small team needs at first.', b: 'Submodules are about nesting repositories, not choosing a team branching model.', d: 'Vendor branches are a specialized technique and were not presented as the default workflow for normal team collaboration.', }, }} />
GitHub Flow, in almost every case. One protected main, a short-lived branch per change, a pull request for review, deploy on merge. It is enough process to keep main releasable and little enough that nobody spends time on branch bookkeeping. GitFlow's develop and release branches only pay off when you genuinely maintain multiple versions in production simultaneously, and adopting it without that need is the most common case of a workflow costing more than it returns.
Only for software with real release management. Desktop applications, on-premise products, anything where several versions are supported at once and hotfixes must land on old releases. Its author has said as much: for continuously deployed web services the extra branches add ceremony without benefit. If you deploy from main several times a week, GitHub Flow or trunk-based development fits the reality better, and the release branch you are maintaining is usually just a delay.
A feature flag is a runtime switch that decides whether new code paths are active, letting you merge unfinished work into main safely because it is simply off. Trunk-based development depends on them: if everyone integrates daily, there is no long branch in which to hide an incomplete feature, so the hiding has to move into the code. The cost is real, flags accumulate, and each one doubles the possible states, so removing them once a feature ships has to be part of the process.
A day or two; a week at the outside. Merge difficulty grows with the square of the time, roughly, because both your branch and main keep moving and the overlap compounds. If a change cannot be finished in that window, split it into separately shippable pieces, a refactor, then the behaviour behind a flag, rather than letting the branch run. "Long-lived feature branch" is close to a synonym for "painful merge".
They are what makes the workflow actually happen rather than being a document people mean to follow. On main, typical rules require a pull request rather than a direct push, at least one approving review, passing status checks, and an up-to-date branch before merge. Setting the merge strategy, squash, merge commit or rebase, at the repository level is worth doing too, since leaving it per-developer produces a history with all three. Enforce it centrally; local hooks can be bypassed with --no-verify.
Configure Git once and stop fighting it: .gitconfig scopes, per-directory identities with includeIf, handy aliases and the defaults worth changing.
Pin one repository inside another with Git submodules: clone with --recurse-submodules, know the two update commands, handle detached HEAD and subtrees.
Write Git commit messages that stay useful: an imperative summary, a body that records why, Conventional Commits and enforcement with commitlint and Husky.