Merging
How to fold a finished feature branch back into main safely, and what makes fast-forward merges different from true merge commits.
What you'll learn
- git merge feature: run it from the target branch, not the feature branch
- Fast-forward vs 3-way merge: what history each strategy produces and when Git chooses each
- How to force a merge commit with --no-ff and clean up merged branches
Before you start
Setting the scene
You have been working on a feature branch. main exists in parallel. At some point the two branches diverge — or they don’t. That single fact determines which of two very different merge strategies Git uses.
Fast-forward merge
A fast-forward merge happens when the target branch (the one you want to merge into) has not received any new commits since the feature branch was created. In other words, the feature branch is simply ahead of main on a straight line.
Git has nothing to reconcile. It moves the main pointer forward to the tip of feature. No new commit is created. The word “fast-forward” is Git telling you: “I just slid the label along — I didn’t have to think.”
# You are on main, which has not moved since feature branched off
git checkout main
git merge feature
Updating 3f1a2b4..c7e9d01
Fast-forward
src/auth.ts | 42 ++++++++++++++++++++++++++++++++++++++
1 file changed, 42 insertions(+)
Notice the word Fast-forward in Git’s output — that is your confirmation.
3-way merge (true merge)
A 3-way merge is required when both branches have advanced since they diverged. Main has new commits; the feature branch has new commits. They share a common ancestor — the last commit that both branches have in common — but they have gone different directions since then.
Git uses three snapshots to work out what the combined result should look like:
- The common ancestor (the fork point)
- The tip of
main - The tip of
feature
Because no single pointer move can represent both lines of work, Git creates a brand-new merge commit. A merge commit is special: it has two parents — one pointing back into main’s history and one pointing back into feature’s history. It is the only commit type that stitches two lines together.
# Both branches have new commits since they diverged
git checkout main
git merge feature
Merge made by the 'ort' strategy.
src/payment.ts | 27 +++++++++++++++++++
1 file changed, 27 insertions(+)
Git will open your editor so you can write (or accept the default) merge commit message. The result looks like the diagram below.
Before and after a 3-way merge
Before the merge, B1 (main) and F1 (feature) diverged from a common ancestor A. After git merge feature, Git creates merge commit M whose two parents are B1 and F1.
Forcing a merge commit with --no-ff
If the branches have not diverged, Git will fast-forward by default. Sometimes that is not what you want — you may want a permanent record in history that a feature branch existed and was merged as a unit.
--no-ff (no fast-forward) tells Git to always create a merge commit, even when a fast-forward would be possible:
git checkout main
git merge --no-ff feature
Merge branch 'feature' into main
# (editor opens for commit message)
Many teams enable this as policy so every feature shows up as a distinct bubble in the commit graph, making history easier to audit.
Cleaning up after a merge
Once the merge is done and pushed, the feature branch has served its purpose. Delete it to keep the repository tidy:
# Delete the local branch (safe — Git refuses if unmerged changes exist)
git branch -d feature
# Verify which branches are already merged into main
git branch --merged main
* main
feature
Any branch listed by git branch --merged is safe to delete — its commits are already reachable from main. A branch that is not listed has work that has not been merged yet; -d will refuse to delete it, protecting you from accidental data loss.
Quick reference
| Scenario | Command | Result |
|---|---|---|
| Merge feature into current branch | git merge feature | Fast-forward or 3-way |
| Always create a merge commit | git merge --no-ff feature | Merge commit regardless |
| Delete merged branch | git branch -d feature | Removes local branch |
| List merged branches | git branch --merged | Safe-to-delete list |
Quick check
Practice this in an interview
All questionsUse a dummy head node to avoid special-casing the result's first element. Walk both lists with two pointers, always appending the smaller current node to the result. When one list is exhausted, append the remainder of the other. O(n + m) time, O(1) space.
concat stacks DataFrames along an axis without matching keys; join aligns on the index (or a single key column) using a convenient shorthand; merge is the most general, joining on any column(s) with full SQL-style control over the join type, key names, and suffix handling.
pandas merge supports inner, left, right, and outer joins that mirror SQL semantics. The validate parameter enforces key cardinality ('one-to-one', 'one-to-many', 'many-to-one', 'many-to-many') and raises MergeError immediately when the data violates the expectation, preventing silent row multiplication.