You're merging your feature branch into main, feeling good about your work, when suddenly Git throws up its hands and says "nope." Red text everywhere. Files marked as conflicted. Your heart sinks.
Take a deep breath. Merge conflicts look scary, but they're actually Git protecting you from making a mistake. When Git encounters conflicting changes, it stops and asks you to decide what the final code should look like.
Why conflicts happen (and why they're good)
Imagine you and a teammate both edited the same paragraph of a document. You changed it one way, they changed it another. If a computer automatically picked one version, someone would lose work. That's exactly what Git refuses to do.
Conflicts happen when:
- The same line is modified differently in both branches
- One branch deleted a file that another branch modified
- One branch deleted lines that another branch changed
- BinaryWhat is binary?A ready-to-run file produced by the compiler. You can send it to any computer and it just works - no install needed. files (images, PDFs) changed in both branches
main branch: const theme = "dark";
feature-A: const theme = "light";
feature-B: const theme = "auto";
Git: "I see three different values for 'theme'. You decide."Reading conflict markers
When Git hits a conflict, it modifies the file in question to show you both versions. These are called conflict markers, and they look intimidating at first but follow a simple pattern:
<<<<<<< HEAD
const color = "blue"; // This is what's in your current branch
=======
const color = "red"; // This is what's in the branch you're merging
>>>>>>> feature-loginLet's break down these markers:
| Marker | Meaning |
|---|---|
<<<<<<< HEAD | Start of your current branch's version |
======= | Separator between the two versions |
>>>>>>> feature-login | End of the incoming branch's version |
HEAD always refers to the branch you're currently on (the one you're merging into). The branch name after the arrows (feature-login in this example) is the branch being merged.
Here's a more realistic example with multiple conflict regions:
function getUserData() {
<<<<<<< HEAD
return fetch('/api/user').then(r => r.json());
=======
const response = await fetch('/api/user');
return await response.json();
>>>>>>> feature-login
}
function formatName(name) {
<<<<<<< HEAD
return name.toUpperCase();
=======
return name.trim().toUpperCase();
>>>>>>> feature-login
}Notice there are two separate conflict regions. Each one needs to be resolved independently.
The conflict resolution workflow
Resolving conflicts is a methodical process. Follow these steps every time:
Step 1: Understand what's conflicted
First, see the scopeWhat is scope?The area of your code where a variable is accessible; variables declared inside a function or block are invisible outside it. of the problem:
git statusYou'll see something like:
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: src/utils.js
both modified: src/components/Header.js
deleted by us: old-styles.cssStep 2: Open each conflicted file
For each file listed, open it in your editor. Search for the conflict markers (<<<<<<<).
Step 3: Decide and edit
This is the critical part. You have three choices for each conflict:
Keep your version (HEADWhat is head?A special pointer in Git that indicates the commit you are currently working on - usually the tip of the active branch.):
const color = "blue";Keep their version (incoming):
const color = "red";Combine both versions:
const color = "purple"; // merge the ideasMost of the time, you'll want a combination. Take this example:
// Conflict:
<<<<<<< HEAD
function greet(name) {
console.log("Hello, " + name);
}
=======
function greet(user) {
console.log(`Hello, ${user}!`);
}
>>>>>>> feature-login
// Resolution - combine the parameter name and template literal:
function greet(name) {
console.log(`Hello, ${name}!`);
}Step 4: Remove all conflict markers
This is crucial, if you leave the markers in, your code won't work:
// BAD - markers still present:
<<<<<<< HEAD
const value = 10;
=======
const value = 20;
>>>>>>> feature-branch
// GOOD - clean resolution:
const value = 15;Step 5: Test your changes
Before you mark the conflict as resolved, test that your code actually works:
npm test # Run your test suite
npm run build # Make sure it compiles
npm run lint # Check for syntax errorsStep 6: Stage and commitWhat is commit?A permanent snapshot of your staged changes saved in Git's history, identified by a unique hash and accompanied by a message describing what changed.
Once the file is fixed and tested:
git add src/utils.js
git add src/components/Header.js
# After all conflicts are resolved
git commit -m "Merge feature-login, resolve API conflicts"Aborting when you're overwhelmed
Sometimes you start resolving conflicts and realize you're in over your headWhat is head?A special pointer in Git that indicates the commit you are currently working on - usually the tip of the active branch.. You can abort the merge entirely:
git merge --abortThis returns your repositoryWhat is repository?A project folder tracked by Git that stores your files along with the complete history of every change, inside a hidden .git directory. to the exact state it was in before the merge started. No harm done.
Tools that help with conflicts
| Tool | Type | Best for |
|---|---|---|
| VS Code | IDE | Built-in merge conflict UI with click-to-resolve buttons |
| IntelliJ/WebStorm | IDE | Powerful 3-way merge view |
| GitKraken | Git GUI | Visual conflict resolution |
git mergetool | CLI | Launch your configured merge tool |
VS Code shows conflict buttons directly in the editor:
[Accept Current] [Accept Incoming] [Accept Both] [Compare]Clicking these buttons resolves the conflict instantly without you manually editing markers.
Prevention: avoiding conflicts in the first place
While conflicts are inevitable, you can reduce them:
Pull before you push. Always pull the latest main before starting work and before merging.
Communicate with your team. If you know someone is working on the same files, coordinate.
Smaller branches, more frequent merges. A branch that lives for two weeks will have more conflicts than one that lives for two days.
Use code ownership patterns. If possible, have clear ownership of different parts of the codebase.
Quick reference
| Command | What it does | When to use it |
|---|---|---|
git status | See conflicted files | First step after conflict |
git diff | See conflict details | Understand the problem |
git add file | Mark file as resolved | After editing |
git commit | Complete the merge | After all files resolved |
git merge --abort | Cancel the merge | Start over |
git mergetool | Open visual merge tool | Complex conflicts |
# When you have a conflict, first see what files are affected
git status
# After editing the file to fix conflicts, stage it
git add conflicted-file.js
# Complete the merge
git commit -m "Resolve merge conflicts"
# Or abort if you want to start over
git merge --abort