A merge conflict is the situation in which a version control system cannot combine two sets of changes automatically, because both changed the same part of a file or made incompatible changes to its structure. Among development tools, it is the point where an automated merge stops and a developer must decide which content is correct.
Conflicts arise in Git during merges, rebases, cherry-picks, reverts and stash applications, and they also appear inside pull requests on hosting platforms such as GitHub. A conflict is not an error in the tool; it is the tool correctly refusing to guess when two lines of development disagree.
How a merge conflict arises
Git merges by performing a three-way merge. It finds the merge base, the most recent common ancestor of the two branches, and compares each branch with that base. A change made on only one side is applied automatically; a region changed differently on both sides becomes a conflict hunk.
When a conflict occurs, Git pauses the operation, writes conflict markers into the affected file and records three versions of it in the index: the base as stage 1, the current branch as stage 2 and the incoming branch as stage 3. The markers look like <<<<<<< HEAD, a separator line ======= and >>>>>>> branch-name, with each side’s text between them.
The merge remains in progress until every conflicted file is resolved, staged with git add and committed. Until then, git status lists the files as unmerged, and git merge --abort or git rebase --abort returns the repository to its state before the operation.
Types of merge conflicts
Not every conflict is a pair of edited lines. Git distinguishes several conflict types, and recognizing the type tells the developer which resolution steps apply.
| Conflict type | Typical cause | How it appears |
|---|---|---|
| Content conflict | Both sides edited overlapping lines | Conflict markers inside the file |
| Modify/delete conflict | One side changed a file the other deleted | File listed as deleted by us or them |
| Rename conflict | Both sides renamed the same file differently | Two candidate paths for one file |
| Add/add conflict | Both sides created a file at the same path | Markers covering the whole file |
| Binary conflict | Both sides changed an image or archive | No markers; one version must be chosen |
| Semantic conflict | Changes merge cleanly but break behavior | No report from Git; tests or build fail |
The semantic conflict is the most dangerous because Git does not detect it. One branch renames a function while another adds a new call to the old name; both merges succeed textually, yet the result does not compile. Only automated tests and continuous integration catch this class reliably.
Resolving a merge conflict
Resolving a conflict means producing the correct combined content, not merely deleting markers. The correct result is often a blend of both sides, and it requires understanding why each change was made.
- Run
git statusto list the unmerged paths. - Open each file, read both sides and the base if needed, and write the intended result.
- Remove every conflict marker and build and test the code.
- Stage the files with
git addand complete the operation withgit commitorgit rebase --continue.
Several tools make this easier. Setting merge.conflictStyle to diff3 or zdiff3 adds the base version to the markers, which shows what each side actually changed. Graphical merge tools in an IDE present the base, both sides and the result in separate panes. The option git rerere records conflict resolutions and reapplies them automatically when the same conflict reappears, which helps during long rebases.
| Command or setting | Effect | Use when |
|---|---|---|
git checkout --ours file | Takes the current branch version | The incoming change is obsolete |
git checkout --theirs file | Takes the incoming version | The incoming change supersedes yours |
git mergetool | Opens a configured visual merge tool | Hunks are large or interleaved |
merge.conflictStyle zdiff3 | Shows the base inside the markers | Intent of each side is unclear |
git rerere | Reuses recorded resolutions | Repeated rebases hit the same conflict |
git merge --abort | Cancels the merge entirely | The merge was started by mistake |
One caution applies to rebasing: during a rebase, ours and theirs are swapped compared with a merge, because Git replays your commits on top of the target branch. Choosing a side by name without checking the content is a common way to discard work.
Preventing and reducing merge conflicts
Conflicts cannot be eliminated, but their frequency and size depend on team practices. The longer two branches diverge, the more overlapping edits accumulate, so the most effective prevention is integrating small changes often.
| Practice | Effect on conflicts | Trade-off |
|---|---|---|
| Short-lived branches | Less divergence from the main line | Requires frequent small pull requests |
| Regular syncing with main | Conflicts surface early and small | More merge or rebase steps |
| Consistent formatting with a linter | Avoids whitespace and style churn | Needs agreed configuration |
| Smaller, focused files | Fewer people edit the same region | More files to navigate |
| Feature flags | Unfinished work merges early in a disabled state | Flag cleanup overhead |
| Generated files excluded or regenerated | Lock files and builds stop conflicting | Build step must be reliable |
Lock files for package managers are a frequent hotspot. A sound approach is to resolve the manifest first and then regenerate the lock file with the package manager rather than editing it by hand.
Common mistakes when handling conflicts
- Committing leftover markers: a forgotten
=======line breaks the build or, in data files, silently corrupts content. - Choosing one side wholesale without reading it, which discards a colleague’s fix.
- Skipping tests after resolution, so a semantic conflict reaches the main branch.
- Force-pushing a rebased shared branch, which rewrites history other people depend on.
- Resolving conflicts in someone else’s code alone when the intent is unclear instead of asking the author.
Many teams add a pre-commit check that rejects conflict markers and require a passing pipeline before a pull request can merge. These two safeguards catch the majority of resolution mistakes before they affect other developers.
In plain words
A merge conflict happens when two people change the same sentence of a shared document in different ways, and the software has to combine their versions. It can merge changes to different paragraphs on its own, but it cannot know which wording of the same sentence is right.
So it stops, shows both versions side by side and asks a person to decide. The tool is being careful, not broken. Once someone writes the final wording and saves it, the two versions become one again and work continues.
A real-world example
Two developers on a team of six work on a web shop. One changes the shipping cost function to add a free-shipping threshold of 50 dollars, while the other, on a separate branch, changes the same function to support a new currency. Both open pull requests on the same day.
The first pull request merges cleanly. When the second developer updates the branch with the main line, Git reports a content conflict in the shipping module covering about 15 lines. With the zdiff3 conflict style enabled, the markers show the original function, the threshold change and the currency change together.
The developer writes a version that applies the threshold after currency conversion, removes the markers and runs the test suite. One test fails because the threshold was compared in the wrong currency, which reveals a semantic issue that neither branch had alone. After a two-line fix, the tests pass and the combined change merges in less than an hour.