When two people edit the same sentence: Merge conflicts are like two people editing the same paragraph in a document simultaneously — Git can not automatically decide which version is correct, so it asks you to choose or combine them manually.
Why it matters: Conflicts are inevitable in team projects. Understanding how to resolve them quickly and correctly prevents lost work, corrupted merges, and frustrated developers.
The key insight: The best way to handle conflicts is to prevent them — small, frequent merges reduce the likelihood of conflicts, and good communication about who is working on what avoids overlapping changes.
A merge conflict occurs when Git’s three-way merge algorithm cannot automatically reconcile changes from two branches. This happens when both branches modify the same region of the same file in different ways.
flowchart TD
A["Both branches modify<br/>different files"] --> B["Auto-merge: no conflict"]
C["Both branches modify<br/>different regions of the same file"] --> B
D["One branch modifies,<br/>other deletes the same file"] --> E["Conflict"]
F["Both branches modify<br/>the same region of the same file"] --> E
G["Both branches add<br/>a file with the same name<br/>(different content)"] --> E
H["One branch renames,<br/>other modifies the original"] --> I["Possible auto-resolve<br/>(rename detection)"]
style B fill:#e8f5e9
style E fill:#ffcdd2
style I fill:#fff3e0 When a conflict occurs, Git writes conflict markers into the file:
int authenticate (User * user ) {
return verify_password (user -> password, user -> stored_hash);
int authenticate (User * user ) {
Token * token = jwt_sign (user, SECRET_KEY);
Marker Meaning <<<<<<< HEADStart of conflict region. Our version begins. =======Separator between our version and their version. >>>>>>> feature-authEnd of conflict region. Their version ends.
The text after >>>>>>> identifies the branch being merged.
When both sides of a conflict have been merged from a common branch, Git may produce multi-base conflict markers (with the ort merge strategy):
||||||| merged common ancestors
The middle section (between ||||||| and =======) shows the common ancestor’s version, which can be helpful for understanding what changed on both sides.
The most common approach — edit the file to produce the correct result:
// After manual resolution:
int authenticate (User * user ) {
// Verify password first (from HEAD)
if ( ! verify_password (user -> password, user -> stored_hash))
// Generate JWT token (from feature-auth)
Token * token = jwt_sign (user, SECRET_KEY);
Then stage and commit:
$ git commit # (or git merge --continue / git rebase --continue)
```bash $ git diff src/auth.c ## Shows the conflict markers and your edits ```## Accept our version (discard their changes)
$ git checkout --ours src/auth.c
# Accept their version (discard our changes)
$ git checkout --theirs src/auth.c
This is useful when:
The other side made a more comprehensive change that supersedes ours. Our change was a mistake that should be discarded. The conflict is in a generated file where one version is correct. Use git merge-file to perform a three-way merge on individual files:
$ git show HEAD:src/auth.c > /tmp/ours.c
$ git show feature-auth:src/auth.c > /tmp/theirs.c
$ git show MERGE_HEAD:src/auth.c > /tmp/base.c # or the merge base
# Three-way merge with a custom base
$ git merge-file /tmp/ours.c /tmp/base.c /tmp/theirs.c
$ git config --global merge.tool meld # Linux
$ git config --global merge.tool vscode # Cross-platform
$ git config --global merge.tool kdiff3 # Cross-platform
Most visual merge tools present a three-pane view:
┌─────────────┬─────────────┬─────────────┐
│ (ancestor) │ (HEAD) │ (branch) │
└─────────────┴─────────────┴─────────────┘
For file types where Git’s line-by-line merge is inappropriate (e.g., JSON, XML, lockfiles), you can define custom merge drivers in .gitattributes:
package-lock.json merge=ours
driver = true # Always succeed, keeping our version
driver = jq -s ' .[0] * .[1] ' %O %A %B
This is particularly useful for:
Lockfiles (package-lock.json``yarn.lock): Always keep one version to avoid corruption.Generated files : Use the generating tool to produce the correct output.When one branch modifies a file and the other deletes it:
CONFLICT (modify/delete): src/legacy.c deleted in feature-auth and modified in HEAD.
Version HEAD of src/legacy.c left in tree.
Resolution options:
Git detects renames using a heuristic (file similarity threshold, default 50%). When one branch renames a file and the other modifies it, Git may or may not auto-resolve:
# Increase rename detection threshold
$ git merge -X find-renames= 80 feature-auth
Git cannot show conflict markers for binary files (images, PDFs, compiled objects). It marks the entire file as conflicted:
$ git checkout --ours image.png
# Or use a specialized tool (e.g., ImageMagick for images)
$ git config merge.img.driver " compare %O %A %B %A "
When both branches create a new file with the same path but different content:
CONFLICT (add/add): Merge conflict in src/utils.c
Resolution: Edit the file to combine both versions, or choose one.
The best way to avoid conflicts is to minimize the chance of two developers modifying the same file simultaneously:
Small, focused branches : Each branch should modify a small number of files.Clear ownership : Assign files or modules to specific developers.Frequent integration : Merge or rebase onto main daily.Use pull requests to signal intent before merging. Announce large-scale changes in team channels. Coordinate refactoring efforts. Repeatedly Reuse Recorded Resolution (rerere) remembers how you resolved a conflict and automatically applies the same resolution if the same conflict recurs:
$ git config --global rerere.enabled true
$ git config --global rerere.autoupdate true
This is particularly useful for:
Long-lived release branches that receive frequent merges from main. Repeated rebase operations where the same conflicts recur. Cherry-picking the same commit to multiple branches. # Restores HEAD and index to pre-merge state
# Works during merge conflicts
# Restores the branch to its pre-rebase state
# Works during rebase conflicts
If you committed a merge with an incorrect resolution:
# Undo the merge commit (keeps changes as unstaged)
Or, if the merge has already been pushed:
# Revert the merge (creates a new commit that undoes it)
$ git revert -m 1 <merge-commit-hash>
Misunderstanding the difference between a stack (LIFO) and a queue (FIFO) in data structure applications.
Forgetting that O ( n log n ) O(n \log n) O ( n log n ) average-case for quicksort becomes O ( n 2 ) O(n^2) O ( n 2 ) worst-case on already sorted input.
Mixing up Big O, Big Ω \Omega Ω , and Big Θ \Theta Θ notation. Big O is an upper bound, not necessarily tight.
Forgetting edge cases in algorithm design (e.g., empty input, single element, already sorted data).
The key principles covered in this topic are linked in the sub-pages above. Focus on understanding the definitions, applying the formulas or frameworks, and evaluating strengths and limitations of each approach.
Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.
Merging : Integration strategy that can produce conflicts requiring resolution.Rebasing : Alternative integration strategy that avoids merge commits but replays commits.Branching : Branch creation and management fundamentals that lead to parallel development.