Back to Git: Branching, Merge, Rebase
Q5Question 5 of 6

Git branching strategy (GitFlow, trunk-based, feature branches) and why maintain separate production and development branches instead of a single branch with tags

Key Concepts
  • GitFlow uses long-lived main and develop branches plus feature, release, and hotfix branches for scheduled releases
  • Trunk-based development keeps everyone on a single main branch with short-lived feature branches and feature flags
  • GitHub Flow is the middle ground: main is always deployable, every change is a reviewed pull request
  • Separate production and development branches let you hotfix live code independently of unreleased work
  • A tag is an immutable pointer to a commit; you cannot commit onto a tag, so hotfixing is clumsy with tags alone
  • The branching model must match the deployment model: if you deploy trunk continuously, trunk IS production

Definition

Pick GitFlow for scheduled releases, trunk-based for continuous delivery, and keep production and development as separate branches so you can hotfix live code independently of unreleased work.
  • GitFlow uses long-lived develop and main branches plus feature, release, and hotfix branches for scheduled releases
  • Trunk-based development keeps everyone on a single main branch with short-lived feature branches merged daily behind feature flags
  • GitHub Flow is the middle ground: main is always deployable and every change is a reviewed pull request
  • Separate production and development branches give a stable, always-deployable line and a separate integration line, enabling independent hotfixes
  • Tags are immutable pointers to commits; you cannot commit onto a tag, so hotfixing with tags alone is clumsy

The National Bank of India keeps two sets of operating procedures. The production binder contains only the exact procedures currently running in every branch where Rajesh and Priya bank: it is sacred and always correct. The development binder is where new procedures are drafted and tested. You keep them separate because if a dangerous bug is found in live procedures, you must fix the production binder immediately without accidentally shipping half-finished drafts.

Understanding the Concepts

What is GitFlow?

GitFlow is the most structured model with two permanent branches and three types of temporary branches.

DIAGRAM
# GitFlow branch structure:
main ─────────────●───────●───────────── (production releases)
                 / \     / \
develop ────●──●────●──●────●──────────── (integration)
           / \      │      / \
feature/  /   \   release /   \
  pay    /     \  /1.2   /     \
        ●───────●       ●───────●
                        hotfix/
                          fix
BASH
# GitFlow workflow:
git checkout develop
git checkout -b feature/upi-transfers
# ... develop the feature ...
git checkout develop
git merge --no-ff feature/upi-transfers

# Release preparation:
git checkout -b release/1.2 develop
# ... stabilize, fix bugs ...
git checkout main
git merge --no-ff release/1.2
git tag -a v1.2.0 -m "Release 1.2.0"
git checkout develop
git merge --no-ff release/1.2

# Hotfix for production:
git checkout -b hotfix/fee-rounding main
# ... fix the bug ...
git checkout main
git merge --no-ff hotfix/fee-rounding
git tag -a v1.2.1 -m "Hotfix: fee rounding"
git checkout develop
git merge --no-ff hotfix/fee-rounding

What is trunk-based development?

Trunk-based development is the opposite philosophy, favoured for continuous delivery. Everyone integrates into a single trunk at least daily.

BASH
# Trunk-based workflow:
git checkout main
git checkout -b feature/upi-transfers
# Feature branch lives hours, not weeks
# ... develop a small increment ...
git push origin feature/upi-transfers
# Open PR, review, CI passes, merge to main
# Deploy main to production (continuous delivery)
JAVA
// Incomplete features hidden behind feature flags
public class TransferService {
    private final FeatureFlags flags;

    public TransferResult processTransfer(TransferRequest req) {
        if (flags.isEnabled("upi-transfers") && req.isUPI()) {
            return processUPITransfer(req);
        }
        return processStandardTransfer(req);
    }
}

How do the strategies compare? (comparison table)

| Aspect | GitFlow | Trunk-Based | GitHub Flow |
|---|---|---|---|
| Permanent branches | main + develop | main only | main only |
| Feature branch life | Days to weeks | Hours to 1 day | Hours to days |
| Release mechanism | Release branch | Deploy from trunk | Deploy from main |
| Hotfix mechanism | Hotfix branch from main | Fix on trunk, deploy | Fix on main, deploy |
| Feature flags | Optional | Required | Optional |
| Best for | Scheduled releases | Continuous delivery | Small teams, web |
| CI complexity | High (many branches) | Low (one branch) | Low |

Why keep separate production and development branches?

A production branch is a living line of development: it has a HEAD you can commit onto. When a critical bug hits live, you branch a hotfix from production, fix it, and deploy, all while development holds unreleased work.

DIAGRAM
# Scenario: critical fee rounding bug in production
# Development branch has an unapproved UPI feature

main (production): v1.2.0 ──── hotfix ──── v1.2.1 (deployed)
                                │
                                └── fix fee rounding only

develop: v1.2.0 ──── UPI feature (not ready to ship)
                      │
                      └── back-merge hotfix from main
BASH
# Hotfix without shipping unreleased work:
git checkout main
git checkout -b hotfix/fee-rounding
# Fix the rounding bug
git commit -m "Fix transfer fee rounding for accounts like SAV2001"
git checkout main
git merge --no-ff hotfix/fee-rounding
git tag -a v1.2.1
# Deploy v1.2.1 -- only the fix, not the UPI feature

# Back-merge so develop gets the fix too
git checkout develop
git merge main

Why can tags not replace a production branch?

A tag is an immutable pointer to one commit. You cannot commit onto a tag. If production is several commits behind the branch HEAD, you cannot add a fix on top of the tagged state without dragging in unreleased commits.

DIAGRAM
# Single branch with tags (problematic):
main: A ── B ── C ── D ── E ── F
           │              │
          v1.2.0        HEAD (has UPI feature)

# To hotfix v1.2.0, you must:
# 1. Create a branch FROM the tag (awkward)
# 2. Fix the bug there
# 3. Merge it back into main
# 4. Now you have an orphaned branch anyway

# With separate branches:
main (production): A ── B ── hotfix (clean, no UPI)
develop:           A ── B ── C ── D ── E ── F (has UPI)

What are common mistakes in branching strategy?

The most common mistake is using GitFlow for a team that deploys continuously, which adds unnecessary ceremony.

TEXT
# WRONG: GitFlow for a team deploying 10 times a day
# Too many branches, too many merges, slows delivery

# WRONG: Trunk-based without CI or feature flags
# Incomplete features break production

# CORRECT: Match the strategy to the deployment model
# Scheduled releases (banking compliance) -> GitFlow
# Continuous delivery (web services) -> Trunk-based
# Small team, simple releases -> GitHub Flow

What should a senior engineer know?

The branching model must match the deployment model. If you truly deploy trunk continuously, trunk IS production and tags suffice. The separate-branch argument is strongest when there is a real gap between what is released and what is in development, which is common in banking where releases are gated by compliance and change windows.

What are common interview follow-up questions?

  • Can you use trunk-based in a bank? Yes, with feature flags and compliance gates in the pipeline.
  • What if a hotfix conflicts with develop? Resolve the conflict during the back-merge. The hotfix always goes to production first.
  • Why does GitFlow slow continuous delivery? Multiple long-lived branches mean more merge conflicts and longer integration cycles.

Visual Overview

DIAGRAM
  GitFlow
  main    ●───────────────●───────── (releases)
  develop ●──●──●──●──●──●──●────── (integration)
           \  /      \   /
  feature   ●●       release
                      hotfix ──●── (from main)

  Trunk-Based
  main    ●──●──●──●──●──●──●──●── (continuous)
           \/ \/ \/ \/ \/ \/ \/
  tiny     ●  ●  ●  ●  ●  ●  ●    (hours, not weeks)
  branches

  Hotfix: Two Branches vs Tags
  ┌─────────────────────────────────────┐
  │ Two branches:                       │
  │ main: A──B──hotfix  (clean fix)     │
  │ dev:  A──B──C──D──E (has new work)  │
  ├─────────────────────────────────────┤
  │ Tags on single branch:             │
  │ main: A──B──C──D──E                │
  │       tag↑    HEAD↑                │
  │ Cannot hotfix tag B without C,D,E  │
  └─────────────────────────────────────┘

Key Takeaway

The branching strategy depends on the team's release model. GitFlow is the structured model with permanent main and develop branches plus feature, release, and hotfix branches; it suits scheduled, versioned releases but is heavy with many merges. Trunk-based development is the opposite: everyone integrates into a single trunk at least daily, feature branches live hours, and incomplete work hides behind feature flags. GitHub Flow is the common middle ground.

On the question of separate production and development branches versus tags on a single branch, the answer is operational capability. A production branch is a living line with a committable HEAD, so you can hotfix the exact live state without dragging in unreleased work. A tag is only an immutable pointer: it records what was released but you cannot commit onto it.

In the National Bank of India project, releases were gated by compliance change windows, so the team kept separate branches. When a critical fee rounding bug hit live while an unapproved UPI feature sat on develop, they cut a hotfix from the exact production state, deployed the fix without shipping the UPI work, and back-merged into develop. That clean separation is what the two-branch model provides.

Remember one sentence: the branching model must match the deployment reality: GitFlow for scheduled releases, trunk-based for continuous delivery, and separate branches when there is a real gap between what is live and what is in development.

---

Share
Use ← → arrow keys to navigate