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:
- Common ancestor (
B) — where the branches diverged - Tip of main (
E) — your current branch - 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
| Strategy | When it happens | Creates merge commit? | History shape |
|---|---|---|---|
| Fast-forward | No divergent commits | No | Linear |
| Three-way | Both branches have commits | Yes | Diamond/fork |
--no-ff | Forced by flag | Always | Diamond/fork |
--ff-only | Forced by flag | Never (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.