Tour of Git
Branching & Merging / Lesson 2.6

Fast-Forward vs Three-Way Merge

When you run git merge, Git picks one of two strategies depending on the branch history. Understanding the difference helps you read the commit graph and predict what will happen.

Fast-Forward Merge

A fast-forward merge happens when the target branch has no new commits since the feature branch was created. Git simply moves the branch pointer forward — no merge commit is needed.

Before:
main:     A --- B
                 \
feature:          C --- D

After (git merge feature):
main:     A --- B --- C --- D
                              ^
                              main, feature

There is no merge commit. The main pointer just "fast-forwards" to where feature already is. The history stays perfectly linear.

When Does This Happen?

  • You create a branch, make commits, and nobody else commits to main in the meantime
  • It is the simplest case — no divergent work exists

Three-Way Merge

A three-way merge happens when both branches have new commits. Git finds the common ancestor and creates a new merge commit that combines both lines of work.

Before:
main:     A --- B --- E
                 \
feature:          C --- D

After (git merge feature):
main:     A --- B --- E --- M
                 \         /
feature:          C --- D

Commit M is the merge commit. It has two parents: E and D. Git uses three points to compute the merge:

  1. Common ancestor (B) — where the branches diverged
  2. Tip of main (E) — your current branch
  3. Tip of feature (D) — the branch being merged

That's why it's called a "three-way" merge.

Forcing a Merge Commit

Sometimes you want a merge commit even when a fast-forward is possible, to preserve the fact that work happened on a branch:

git merge --no-ff feature

This creates a merge commit regardless:

main:     A --- B --------- M
                 \         /
feature:          C --- D

Many teams prefer --no-ff because it keeps the branch history visible in the graph.

Forcing a Fast-Forward Only

If you want to ensure no merge commit is created (and fail if one would be required):

git merge --ff-only feature

This is useful in scripts or CI pipelines where you want linear history and prefer to rebase instead of merge.

Summary

StrategyWhen it happensCreates merge commit?History shape
Fast-forwardNo divergent commitsNoLinear
Three-wayBoth branches have commitsYesDiamond/fork
--no-ffForced by flagAlwaysDiamond/fork
--ff-onlyForced by flagNever (fails if not possible)Linear

Key Insight

Neither strategy is "better" — they serve different needs. Fast-forward keeps history clean and linear. Three-way merges preserve the context of feature branches. Most teams pick a convention and stick with it.