How to resolve a merge conflict properly.

•

•

Read Both Sides Before You Pick One

I once spent a Tuesday evening staring at a terminal screen, watching a deployment fail because I thought I knew better than the git markers. I had “resolved” a conflict by simply deleting the incoming changes, thinking I was being efficient, only to realize three hours later that I’d wiped out a critical payment validation logic that someone else had spent a week perfecting. Most tutorials tell you how to use the tools, but they never teach you how to resolve a merge conflict properly without accidentally sabotaging your teammates. They treat it like a syntax problem, when in reality, it’s a communication problem disguised as a technical one.

I’m not going to give you a lecture on git commands you can find in a manual. Instead, I want to talk about the mental framework you need so you don’t end up as the person breaking the build at 2:00 AM. I’ll show you how to approach a conflict by understanding the intent behind the code, rather than just making the red text go away. This is about building a process that is repeatable, boring, and—most importantly—safe for production.

Table of Contents

Why Your Git Merge Conflict Workflow Failed You

Why Your Git Merge Conflict Workflow Failed You

Most teams treat merge conflicts like a nuisance to be bypassed rather than a signal of a systemic issue. I’ve seen it happen a dozen times: a developer gets stuck in a massive, multi-file collision, grows frustrated, and starts blindly choosing “Accept Incoming” just to make the red text go away. This is how you end up with a broken build that only manifests once it hits the staging environment. The problem usually isn’t the tool; it’s a git merge conflict workflow that relies on brute force rather than communication. When you’re just trying to clear the screen, you aren’t actually resolving the logic; you’re just hiding the evidence of a collision.

The real culprit is often the scale of the changes being introduced. If you’re waiting three days to integrate a massive feature branch, you aren’t just asking for a conflict; you’re asking for a nightmare. I learned this the hard way at a logistics startup where we had two engineers handling overlapping code changes in the same core routing service for an entire sprint. By the time we tried to merge, the logic was so intertwined that no automated tool could save us. We spent a whole afternoon untangling a mess that could have been avoided if we had just practiced more frequent, smaller integrations.

The High Cost of Handling Overlapping Code Changes

The High Cost of Handling Overlapping Code Changes.

The real cost isn’t just the time you spend staring at red and green lines in your IDE; it’s the invisible technical debt you accrue when you rush through handling overlapping code changes. I’ve seen it happen more times than I’d like to admit: two engineers touch the same service layer, a conflict arises, and instead of truly understanding the logic change, one person just “picks theirs” to make the build pass. You aren’t actually fixing the code; you’re just silencing the warning.

This is where the logic breaks. When you’re resolving version control conflicts by simply deleting the “wrong” lines without checking the context, you end up with a codebase that passes CI but fails miserably in production under specific edge cases. I once spent an entire weekend tracing a race condition that turned out to be the result of a “quick fix” during a messy merge three weeks prior. The developer thought they were just resolving a trivial conflict, but they actually unwound a critical safety check that another teammate had implemented. If your workflow treats conflicts as a nuisance to be cleared rather than a signal that two people are trying to change the same reality, you’re just waiting for a 2:00 AM incident report.

My rules for not making things worse while fixing them

  • Stop trying to resolve conflicts in the IDE’s “magic” UI. When the diffs get messy, open the actual file in a plain text editor. You need to see the context around the markers, not just the highlighted lines, otherwise you’ll miss a stray closing brace that kills the whole build.
  • Talk to the person who wrote the other side. If you see a block of code you don’t recognize, don’t just pick “incoming” or “current” because it looks cleaner. Ping them. It takes two minutes to ask “did you mean to change this logic?” and saves four hours of debugging a regression later.
  • Treat the resolution like a new feature. Once you’ve cleaned up the markers, don’t just assume it works. Run your local test suite specifically against the modules you touched. If you’re merging a complex logic change, a green build on the CI doesn’t mean you didn’t accidentally delete a side effect.
  • Small, frequent merges are your only real defense. I’ve learned the hard way that waiting three days to pull the main branch into your feature branch is a recipe for a nightmare. If you merge daily, the conflicts are tiny, boring, and easy to solve. If you wait a week, they become structural.
  • Write a “fix-up” commit if you have to. If the merge was particularly hairy, don’t try to make one perfect, massive commit that resolves everything. If you had to make a weird architectural compromise to get the code to compile, commit the merge first, then immediately follow it with a second commit that explains exactly what you had to tweak to make it work.

The Lessons I Learned the Hard Way

Stop treating merge conflicts as a syntax error to be fixed; treat them as a logic collision that requires a conversation with whoever wrote the other side.

Never resolve a complex conflict in a rush to get a PR merged; if you can’t explain exactly why you chose line A over line B, you shouldn’t be committing it.

Small, frequent merges are a pain in the neck daily, but they are significantly cheaper than the three-hour post-mortem I had to lead after a “quick fix” deleted a critical payment validation logic.

The Long View on Git

The Long View on Git merge conflicts.

At the end of the day, resolving a merge conflict isn’t about being a Git wizard or knowing every obscure flag in the CLI. It is about slowing down enough to realize that you aren’t just moving lines of text around; you are reconciling two different intentions for how the system should behave. If you rush the resolution just to get the green checkmark on your PR, you are essentially planting a landmine for whoever has to deal with the fallout during the next deployment. Stop treating conflicts as an annoyance to be bypassed and start treating them as critical signals that your team’s architectural boundaries are overlapping in ways you didn’t expect.

I still have a few half-finished side projects sitting in a folder on my machine, mostly because I hit a wall and didn’t want to deal with the friction. But in a professional production environment, the friction is often where the most important learning happens. Don’t aim for the fastest merge; aim for the merge that lets you sleep through the night. If you can build a workflow where you prioritize context over speed, you’ll find that the 2:00 AM pages become a lot less frequent. It’s not about being perfect, it’s just about being deliberate enough to leave the codebase better than you found it.

Frequently Asked Questions

What do I do when the conflict is in a file I haven't touched in six months and I don't even remember the original context?

This is where most people panic and just pick “Accept Incoming.” Don’t do that. If I don’t recognize the code, I treat it like a crime scene. First, I use `git blame` to see who actually touched it last. If it wasn’t me, I find them. If they’re gone, I look at the surrounding logic. If the context is still a total mystery, I don’t guess—I pull the person who owns that service into a quick call. Better a ten-minute chat than a two-hour rollback.

Is it actually better to abort the merge and rebase instead of trying to fix a massive conflict manually?

In my experience, yes. When a conflict gets massive, you’ve lost the mental model of what the code is supposed to be doing. Trying to manually stitch it together is how you accidentally delete a critical null check or leave a stray bracket that breaks the build. I usually `git merge –abort` and rebase instead. It forces you to deal with the changes in smaller, logical increments, which is much harder to mess up.

How do I know if I've actually fixed the logic correctly, or if I've just satisfied the compiler but broken the business rule?

The compiler only cares about syntax, not whether your payment logic still makes sense. To know if you’ve actually fixed it, you have to look past the green checkmarks. I always run my local test suite, but specifically, I look for the edge cases I wrote into the unit tests months ago. If the logic changed, the existing tests should fail. If they still pass, you probably just patched the syntax and left a bug lurking.

At what point does a conflict become too large to solve safely on a local machine, and should I be asking for help instead of pushing through?

If you’re staring at a diff and you can’t remember if the logic you’re deleting was a bug fix from six months ago or a critical edge case for a client, stop. When the conflict spans more than a few dozen lines of core business logic—or touches multiple files that should stay decoupled—you’re no longer merging; you’re rewriting. At that point, put the keyboard down. Ping the author. It’s better to look “slow” than to spend the night fixing a regression you introduced.

Tomasz Wierzbicki

About Tomasz Wierzbicki

I am not interested in arguing about frameworks. I am interested in why the thing broke at two in the morning and what would have prevented it. Most of what I know came from being wrong in production and writing it down afterwards.