Skip to main content

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>, then git commit (or git rebase --continue if rebasing).
  • git merge --abort cancels mid-merge and restores both branches to their previous state.
  • git mergetool opens a visual 3-pane editor if you prefer that over editing raw markers.

Quick example

bash
# 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:

  • <<<<<<< HEAD to ======= is your current branch
  • ======= to >>>>>>> branch-name is 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 mergetool with a visual tool like Meld or VS Code. Three panes side by side: base (ancestor), ours, theirs.
  • Changed your mind mid-resolve: git merge --abort resets 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.png or git checkout --theirs file.png to pick one version explicitly.
  • Conflicts during git rebase: same markers, but you resolve per commit and run git rebase --continue after each one instead of git commit.

Configure diff3 for more context

By default the conflict block shows only two sides. Run this once:

bash
git config --global merge.conflictstyle diff3

Now 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.

bash
# 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.json dependency conflicts during PR merges are the most common case across teams.
  • Node/Express: two developers adding route handlers to app.js at the same location.
  • Kubernetes: deployment.yaml where 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 mergetool with 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

bash
# 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

jsx
// 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:

jsx
<<<<<<< HEAD const App = () => <div>{user.name} ({user.role})</div>; ======= const App = () => <div>{user.name} - {user.email}</div>; >>>>>>> feature

Both changes are valid. Combine them:

jsx
const App = () => ( <div>{user.name} ({user.role}) - {user.email}</div> );
bash
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

bash
# 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.

bash
# 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 ready
Premium

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

Finished reading?