How to resolve merge conflicts in Git?
Merge conflict - a state where Git cannot auto-combine two branches because both modified the same lines differently, so it stops and asks you to decide.
Theory
TL;DR
- Two chefs editing the same recipe line: one writes "1 tsp salt", the other "2 tsp". Git stops and asks you to pick or blend.
- Git marks conflicts with
<<<<<<<,=======,>>>>>>>and pauses until you fix them manually. - After editing:
git add <file>, thengit commit(orgit rebase --continueif rebasing). git merge --abortcancels mid-merge and restores both branches to their previous state.git mergetoolopens a visual 3-pane editor if you prefer that over editing raw markers.
Quick example
# main has: price: 10
# feature branch changed it to: price: 12
# main also changed it to: price: 15
git checkout main
git merge feature
# CONFLICT (content): Merge conflict in file.txt
# file.txt now shows:
<<<<<<< HEAD
price: 15
=======
price: 12
>>>>>>> feature
# Edit file.txt: remove all three markers, write the right value:
price: 15 + tax (12 base)
git add file.txt
git commit -m "Merge with tax adjustment"You removed all three markers and wrote clean code. That is the whole loop.
How Git finds the conflict
Git runs a three-way merge: it compares your branch (HEAD), the incoming branch, and their common ancestor via git merge-base. If only one side changed a line, Git takes that change automatically. If both sides changed the same line differently, Git cannot decide. So it stops, writes conflict markers into the file, and waits.
The markers show exactly what each side wrote:
<<<<<<< HEADto=======is your current branch=======to>>>>>>> branch-nameis the incoming branch
After you edit and stage the file, the index holds the resolved version. The commit finalizes it.
When to use which approach
- Single file, few conflicts: open in your editor, fix manually,
git add,git commit. - Many files or complex diffs:
git mergetoolwith a visual tool like Meld or VS Code. Three panes side by side: base (ancestor), ours, theirs. - Changed your mind mid-resolve:
git merge --abortresets everything back. Safe to run at any point before the final commit. - Binary files (images, compiled assets): Git cannot merge them. Use
git checkout --ours file.pngorgit checkout --theirs file.pngto pick one version explicitly. - Conflicts during git rebase: same markers, but you resolve per commit and run
git rebase --continueafter each one instead ofgit commit.
Configure diff3 for more context
By default the conflict block shows only two sides. Run this once:
git config --global merge.conflictstyle diff3Now the conflict block adds a third section showing what the common ancestor had. That context often makes it obvious which side is more correct without reading full branch history.
Common mistakes
Committing without staging first.
# Wrong - after editing conflicted.js:
git commit -m "fixed"
# Error: nothing to commit
# Right:
git add conflicted.js
git commit -m "Resolve price conflict"Git needs the staged version to know the conflict is resolved. Without git add, it does not know you finished.
Picking one side without reading both.
If a teammate fixed a bug on their branch and you blindly take your version with git checkout --ours, you lose their fix. Read both sides. git diff HEAD file.js shows what your branch actually has.
Leaving marker characters in the code.
<<<<<<< HEAD is plain text. If it ends up in your JavaScript or JSX, the app breaks with a syntax error. After resolving, do a quick search for <<<<<<< in the file to confirm all markers are gone. I have seen this slip through code review more times than I would expect.
Forgetting git status after a multi-file merge.
Git lists every unresolved file under "both modified". Fix one file, forget to check, commit without it, and now your next git status has unresolved files blocking further work.
Using git merge --continue without staging.
--continue checks the index for staged resolutions. If nothing is staged, it throws an error. Stage first, then continue.
Real-world usage
- React / Next.js:
package.jsondependency conflicts during PR merges are the most common case across teams. - Node/Express: two developers adding route handlers to
app.jsat the same location. - Kubernetes:
deployment.yamlwhere ops edits resource limits and dev edits the image tag on the same lines. - Monorepos: lock files (
package-lock.json,yarn.lock) conflict on almost every merge. Most teams skip manual resolution and just regenerate the file after merging. - Linux kernel contributions:
git mergetoolwith vimdiff for large patch sets across hundreds of files.
On shared branches, use git merge to preserve history. If you are working alone on a feature branch, git rebase main gives a clean linear history and avoids a merge commit.
Follow-up questions
Q: What is the difference between a merge conflict and a rebase conflict?
A: The conflict markers look identical, but the timing differs. A merge resolves all conflicts in one commit. A rebase replays each commit on top of the target branch, so you may hit conflicts once per commit and run git rebase --continue after each one.
Q: How do you prevent conflicts in a team?
A: Short, frequently merged branches. git pull --rebase daily so your branch stays close to main. Trunk-based development removes long-lived branches entirely, which cuts conflict frequency more than any other single practice.
Q: What does git mergetool do that manual editing does not?
A: It opens a visual diff with three panes: ancestor, your version, incoming version. You pick hunks or type the merged result in a fourth output pane. Configure it with git config merge.tool meld or git config merge.tool vscode.
Q: How do you handle a conflict in a binary file like an image?
A: Git marks it as unmergeable. Pick one version explicitly: git checkout --ours logo.png or git checkout --theirs logo.png, then git add logo.png and commit.
Q: (Senior) How does merge.conflictstyle=diff3 help, and when does Git's rename detection fail?
A: diff3 adds the ancestor version inside the conflict block, giving context to understand why each side changed. Rename detection fails when file similarity drops below 50% after edits. Git then treats the file as deleted on one branch and new on the other, producing a "modify/delete" conflict. Fix it manually: git rm file1.txt, apply edits to the renamed file, git add file2.txt, commit.
Examples
Basic: single-line conflict
# main branch has:
echo "version: 1.0" > app.txt
git commit -am "initial version"
# feature branch changes to 2.0
git checkout -b feature
echo "version: 2.0" > app.txt
git commit -am "update version"
# main independently changes to 1.5
git checkout main
echo "version: 1.5" > app.txt
git commit -am "patch version"
git merge feature
# CONFLICT (content): Merge conflict in app.txt
# app.txt now shows:
<<<<<<< HEAD
version: 1.5
=======
version: 2.0
>>>>>>> feature
# Pick the right one, remove all markers:
echo "version: 2.0" > app.txt
git add app.txt
git commit -m "Resolve version conflict: take 2.0"Both branches edited the same line. You read both, decided 2.0 is correct, removed markers, staged, committed.
Intermediate: React component with multiple props
// src/App.js on main - added role display:
const App = () => <div>{user.name} ({user.role})</div>;
// feature branch added email:
const App = () => <div>{user.name} - {user.email}</div>;After git merge feature:
<<<<<<< HEAD
const App = () => <div>{user.name} ({user.role})</div>;
=======
const App = () => <div>{user.name} - {user.email}</div>;
>>>>>>> featureBoth changes are valid. Combine them:
const App = () => (
<div>{user.name} ({user.role}) - {user.email}</div>
);git add src/App.js
git commit -m "Merge feature: show role and email"Run the app after committing. A JSX syntax error at this point almost always means a marker character survived in the file.
Advanced: rename plus edit conflict
# main renames file1.txt to file2.txt
git mv file1.txt file2.txt
git commit -m "rename file1 to file2"
# feature branch edited file1.txt before the rename was merged
git checkout -b feature main~1
echo "new content" >> file1.txt
git commit -am "update file1"
git checkout main
git merge feature
# CONFLICT (modify/delete): file1.txt deleted in HEAD and modified in feature.Git's rename detection uses a 50% similarity threshold. When file1.txt has heavy edits on the feature branch, Git may see it as deleted on main and modified on feature, producing a "modify/delete" conflict instead of a normal content conflict.
# Accept the rename, apply the edit to the new filename:
git rm file1.txt
# Manually add the new content to file2.txt, then:
git add file2.txt
git commit -m "Resolve rename + edit conflict"Developers who see "modify/delete" often assume something broke. Nothing broke. The file moved, and Git needs your help connecting the edit to the new location.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.