Source code: Minimizing merge conflicts

Posted: 2025-09-09

Introduction

Teams making fast progress on any shared codebase inevitably face (source code) merge conflicts. This text describes general strategies to improve the situation.

This deliberately doesn’t cover specific tools (such as Git or Mercurial). Teaching a specific tool is not in scope for this document.

Types of conflicts

I distinguish two types of conflicts:

In my experience, the majority of conflicts are textual. These strategies aim to deal with both types.

Accept conflicts

Conflicts are a normal part of the development cycle, especially easy conflicts. Acknowledging this and adjusting your workflow accordingly goes a long way to mitigate their impact.

Anticipating conflicts helps you reduce the frustration you feel when they occur.

What does this mean in practice?

The value of a change in flight is not the specific artifact you’ve produced (the code change) but the knowledge of all the aspects involved. Be ready to throw away a specific implementation of the change and re-apply it “manually” to a new head.

Small changes

Make each code change as small and focused as possible. Avoid bundling logically separate steps into the same code change. Small changes lets you reduce the impact of conflicts.

Make sure your workflow supports creating chains of dependent changes.

Reduce in-flight work

The probability of conflicts is proportional to the amount of in-flight work (i.e., the number of code changes started but uncommitted). Therefore, reducing the amount of changes in-flight will also reduce the amount of conflicts.

The core principle is to prioritize finishing work over starting new work.

To get changes committed as efficiently as possible, apply these heuristics, in order:

Defer work to the future

There’s a balance to be found between:

The best trade-offs are team- and project-dependent, but submitting code sooner reduces in-flight work and thus avoids conflicts.

This means you should prefer to mark potential improvements with appropriate TODO comments and get code submitted as soon as it meets a certain bar of quality (i.e., as long as it doesn’t break existing functionality).

As a reviewer, be liberal in your approvals. When you spot a potential improvement, distinguish these two situations:

Overcommunicate

A dedicated team channel can prevent many logical conflicts. If you are going to change a significant or potentially disruptive change, announce this.

This is especially critical for changes to widely used interfaces or core logic. A simple message can save hours of rework:

I intend to change interface WorkflowOrchestrator on Wednesday. Please try to avoid changes that depend on this interface. I’ll let you know as soon as my change is in.

Rebase incrementally

For changes that take a while to be committed, rebase them on head frequently, resolving small conflicts incrementally. This works much better than facing a giant conflict at the end.

Re-apply, not merge

If you’re facing a large textual conflict, remember that the real value of your change is not the specific patch you wrote, but the knowledge and understanding you gained as you wrote it.

This imples that sometimes it’s easier to throw away your change, and “manually” re-implement your solution on head.

Design stable interface

Well-designed software architexture can dramatically reduce conflicts. Aim to build modules with narrow, stable interfaces that change rarely. By allowing different engineers (or sub-teams) to take ownership of different modules, this can have a huge impact in your ability to parallelize work.

Conclusion

Merge conflicts are a natural byproduct of collaborative development, but they don’t have to be a major drain on productivity. By embracing development practices like limiting work in progress, keeping changes small, communicating effectively, and designing stable interfaces, your team can significantly reduce their frequency and impact and focus on what truly matters: building great software.