Skip to main content

What is the difference between git merge and git rebase?

git merge integrates a branch by creating a new commit with two parents, keeping the full recorded history of both branches. git rebase replays your commits on top of another branch, rewriting their SHA values to produce a linear sequence.

Theory

TL;DR

  • Analogy: merge is stapling two reports together, both paths stay visible. Rebase is rewriting your notes onto the end of the main report so it looks like one continuous document.
  • Main difference: merge adds one commit with two parents; rebase creates new commits with new SHA values for each one it replays.
  • Merge shows exactly when branches diverged and converged. Rebase makes it look like the work happened sequentially from the start.
  • Decision rule: shared or public branch? Use merge. Local feature branch before a PR? Use rebase.
  • After pushing to a shared remote: merge only. Rebasing after push breaks teammates' clones.

Quick example

bash
# Setup: main has commit B, feature branch has D and E git checkout main && echo "B" >> app.js && git commit -m "B" git checkout -b feature echo "D" >> app.js && git commit -m "D" echo "E" >> app.js && git commit -m "E" # Option 1: merge git checkout main && git merge feature # History: A-B-M (M has two parents: B and E) # Option 2: rebase (run on the feature branch, before merging) git checkout feature && git rebase main # History: A-B-D'-E' (D' and E' are new commits with new SHAs)

After merge the log shows a fork and a reunion. After rebase it is a straight line. Same code in both cases, but a different recorded story.

Key difference

Merge finds the common ancestor of both branches, computes a three-way diff, and records a single new commit with both tips as parents. The original commits stay untouched. Rebase takes each commit between the fork point and the feature tip, strips it from its original parent, and replays it onto the target via cherry-pick. Because the parent SHA changes, the commit SHA changes too, even when the actual code diff is identical. That is why rebasing a branch others have already cloned causes problems.

When to use

  • Shared branch (main, develop): merge. Always. Rebasing a branch others pulled creates duplicate commits and forces everyone to fix their local state manually.
  • Local feature branch before opening a PR: rebase onto main first. The reviewer gets a clean, sequential story.
  • Public history already pushed and pulled by a teammate: merge. Even on your own feature branch.
  • Interactive rebase (git rebase -i): fine for squashing or editing commits before the very first push.

Comparison table

Aspectgit mergegit rebase
History shapeBranched, shows divergenceLinear, commits in sequence
New commitsOne merge commit (two parents)One new commit per replayed commit, new SHAs
Rewrites history?NoYes
Safe after push to shared branch?YesNo
Conflict resolutionOnce, at the merge pointOnce per replayed commit
When to useShared branches, audit trailsLocal feature branches, clean PRs

How it works internally

Merge computes a three-way diff: the common ancestor commit plus the two branch tips. With no conflicts, it records one commit with two parents automatically. Rebase walks the commits between the fork point and the feature tip, cherry-picks each one onto the new base, and writes fresh commits. Because each replayed commit has a different parent, it gets a different SHA, even if the actual code change is byte-for-byte identical.

Common mistakes

Rebasing a shared branch.

bash
# Wrong git checkout main && git rebase feature # Rewrites public main

Every teammate who already pulled main now has a diverged history. Their git pull fails. They need git reset --hard origin/main to recover. For any branch the team is actively using, stick to git merge.

Expecting a single conflict resolution step during rebase.

bash
git rebase main # Conflict on D -> fix it -> git rebase --continue # Conflict on E -> fix it -> git rebase --continue

Merge surfaces all conflicts in one step. Rebase pauses at each conflicting commit. Developers expecting a single resolution step abort the rebase thinking something is broken. It is not. Use git rebase --continue after each conflict, or git rebase --abort to get back to the original state.

Force-pushing after rebase when a teammate already pulled.

bash
git push origin feature # Teammate pulls this git rebase main # New SHAs on feature now git push --force-with-lease # Overwrites the remote # Teammate's local feature branch is now orphaned

--force-with-lease checks that the remote tip matches your last fetch. It does not protect a teammate who pulled between your fetch and your push. On a shared branch this is always a problem, not just an edge case.

Merge after rebase without thinking about fast-forward.

After rebasing feature onto main, the feature tip is directly ahead of main. git merge feature fast-forwards automatically and adds no merge commit. If you want a merge commit to mark the PR boundary in the log, add --no-ff explicitly:

bash
git checkout main && git merge --no-ff feature

Real-world usage

  • React (facebook/react): contributors rebase feature branches locally before submitting PRs. The commit graph on main stays linear.
  • Node.js core (nodejs/node): merge commits for stable branches (v20.x), interactive rebase for personal work in progress.
  • Linux kernel (torvalds/linux): strict rebase policy for contributors before Linus merges. Linear history makes git bisect work cleanly across thousands of commits.
  • Express.js: rebase plus merge --no-ff on PR merge to keep the PR boundary visible in the log.

Most teams land somewhere between these. "Rebase before PR, merge to main" covers the majority of real situations without much policy enforcement.

Follow-up questions

Q: How do you verify that rebase created new commits?
A: Run git reflog right after. You will see the old SHA values before the operation and the new ones after. git log --oneline --graph also shows the linear result.

Q: What does --force-with-lease do and when is it safe?
A: It pushes only if the remote tip matches the last SHA you fetched. Safe when you are the sole developer on the branch and nobody has pulled since your last fetch.

Q: How does conflict resolution differ between merge and rebase?
A: Rebase pauses at each commit that causes a conflict. You fix it and run git rebase --continue to move to the next one. Merge collects all conflicts and presents them in a single resolution step.

Q: What is git pull --rebase origin main and how is it different from git rebase main?
A: git pull --rebase fetches the latest commits from the remote first, then rebases your local branch onto the updated remote tip. git rebase main only rebases onto your local main, which may be behind the remote.

Q: In a large monorepo with many contributors, why does merge scale better than rebase?
A: Replaying hundreds of commits from many contributors generates frequent per-commit conflicts. Each merge integration is a single commit, so conflicts stay contained. Merge also preserves the contributor timeline, which matters for git blame and compliance audits.

Examples

Merge in a Node.js Express app

bash
git checkout main echo "app.get('/', (req,res) => res.send('Home'));" > server.js git add . && git commit -m "Initial Express server" git checkout -b auth-feature echo "app.use('/admin', authMiddleware);" >> server.js git add . && git commit -m "Add auth route guard" echo "function authMiddleware(req,res,next){ next(); }" >> server.js git add . && git commit -m "Implement auth check" git checkout main git merge auth-feature # Result: merge commit with two parents # git log --graph shows the branch point and the merge

Both original commits stay intact with their original SHA values. You can see exactly when auth-feature branched off main and when it landed back.

Rebase before opening a PR

bash
# main got new commits while you were working on auth-feature git checkout auth-feature git rebase main # auth-feature commits are now replayed on top of the latest main # git log --graph: linear - Initial -> new main commits -> auth commits git push origin auth-feature git push --force-with-lease # Only needed if you pushed this branch before

The code is the same as after a merge. But the history reads as if the auth commits were written after the latest changes to main, which makes the PR simpler to review.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?