Merge conflicts happen when Git can’t automatically combine changes from two branches because those changes overlap in ways it can’t resolve on its own. The most common trigger is straightforward: two people edited the same line in the same file on different branches. Git has no way to know which version is correct, so it stops and asks you to decide.
How Git Decides What Conflicts
When you merge two branches, Git performs what’s called a three-way merge. It compares both branches against their most recent common ancestor, the point where the branches originally split apart. If Branch A changed line 14 of a file and Branch B didn’t touch it, Git simply takes Branch A’s version. If both branches changed different files, or even different parts of the same file, Git combines them without complaint. The system is surprisingly good at this.
A conflict only arises when Git finds changes it can’t reconcile automatically. Two conditions cause this. First, both branches modified the same line (or adjacent lines) in the same file. Second, one branch deleted a file that the other branch modified. In either case, Git doesn’t guess. It marks the file as conflicted, inserts both versions into the file with special markers, and waits for you to sort it out.
Git can also refuse to even start a merge if you have uncommitted changes in your working directory or staging area. This isn’t a conflict with someone else’s code. Git is protecting you from losing your own unsaved work. Commit or stash those changes first, and the merge will proceed.
Four Types of Merge Conflicts
Not all conflicts look the same. Understanding the type helps you resolve them faster.
- Content conflicts: The classic case. Two branches changed the same lines in the same file differently. Git shows you both versions inline and asks you to pick one, combine them, or write something new.
- Modify/delete conflicts: One branch edited a file while the other branch deleted it entirely. Git doesn’t know whether to keep the edited version or honor the deletion.
- Rename/rename conflicts: Both branches renamed the same file, but gave it different new names. Git can’t choose which name wins.
- Rename/delete conflicts: One branch renamed a file and the other deleted it. Git can’t decide whether the renamed file should exist or not.
Content conflicts are by far the most frequent. The others tend to crop up during large refactors or reorganizations where files are moving around.
Why Some Projects Get More Conflicts Than Others
The technical mechanism is only half the story. How your team works determines how often conflicts actually surface.
Long-lived feature branches are the single biggest contributor to frequent, painful conflicts. The longer a branch lives, the more the main codebase changes underneath it. Every day your branch stays separate, the odds increase that someone else touched the same code you’re working on. Even if you regularly pull updates from the main branch into your feature branch (sometimes called “reverse integrating”), you’re still at risk because other developers also have their own long-lived branches that haven’t been merged yet. The problem compounds: divergence grows in every direction at once.
Large commits make things worse. A commit that touches 40 files across the codebase has a much higher chance of overlapping with someone else’s work than a small, focused change to two or three files.
Team size and code ownership also play a role. If five developers all work in the same module with no coordination, conflicts are nearly guaranteed. If each person owns a distinct area of the codebase, they’ll rarely step on each other’s changes.
How Git’s Merge Strategy Works Under the Hood
Git’s default merge strategy for combining two branches is called “recursive.” It builds a merged tree from the common ancestors of both branches and uses that as the reference point for the three-way comparison. This approach was designed to handle complex branch histories, and testing on the Linux kernel’s development history showed it produces fewer merge conflicts without introducing incorrect merges.
Modern versions of Git are transitioning to a newer strategy called “ort” (short for “Ostensibly Recursive’s Twin”). It handles edge cases better and runs significantly faster in large repositories, particularly when many file renames are involved. For most everyday merges, you won’t notice a difference between the two. Both follow the same core logic: if the same region of a file was changed on both sides, it’s a conflict.
Textual vs. Semantic Conflicts
There’s an important distinction most developers don’t think about until it bites them. A textual conflict is the kind Git catches: two branches changed the same lines, Git can’t merge them, and it tells you. But a semantic conflict is sneakier. Git merges the code successfully with no warnings, yet the result is broken. Maybe one branch renamed a function while another branch added a new call to it using the old name. Git sees changes in different parts of different files and happily combines them. The code compiles, or maybe it doesn’t. Tests fail. Nothing in the merge output warned you.
This is why running your test suite after every merge matters, even when Git reports no conflicts. Text-based merging tools, including Git itself, only compare lines of text. They have no understanding of what the code actually does.
Reducing Conflicts Before They Happen
The most effective way to avoid merge conflicts is to merge more often, not less. Trunk-based development, where every developer merges small updates into a single main branch frequently (often daily), consistently produces fewer conflicts than workflows with long-lived branches. The logic is simple: smaller changes merged more often means less divergence at any given moment.
A few practical habits that make a real difference:
- Keep branches short-lived. A branch that lives for two days will almost never conflict. A branch that lives for two weeks probably will.
- Pull from main frequently. If you do need a longer-lived branch, update it from the main branch at least daily. You’ll catch overlapping changes early when they’re small and easy to resolve.
- Make small, focused commits. A commit that changes one thing in one area of the code is far less likely to collide with a teammate’s work than a sweeping change across multiple files.
- Communicate about shared files. If you know you’re about to refactor a core module, let your team know. A quick heads-up prevents the worst conflicts.
- Commit before merging. Uncommitted local changes will block Git from even starting a merge. Keep your working directory clean.
Merge conflicts aren’t a sign that something went wrong with Git or with your code. They’re a natural consequence of parallel work. Git flags them precisely because it refuses to silently pick one version over another. The goal isn’t to eliminate conflicts entirely, which is unrealistic on any team. It’s to keep them small, infrequent, and easy to resolve by integrating your work early and often.

