Open any job ad, any process document, any “engineering ladder” today and you will find the same holy words:
“Strong code-review culture”
“PRs must have at least two approvals”
“100% reviewed code before merge”
We have normalised Merge Requests (PRs/MRs) so completely that most teams literally cannot imagine shipping production code without them.
They are treated as the ultimate quality gate, the place where knowledge spreads, where bad code dies, where junior devs level up, and where risk is contained.
They are none of those things.
In almost every normal product team they are an expensive, velocity-killing, quality-reducing anti-pattern — and the evidence is hiding in plain sight in every stand-up you have ever attended.
Here is the unvarnished truth.
1. The merge itself creates new, never-before-executed software
Your branch passed all tests.
Main passed all tests.
The result after git merge is a third program that has literally never run on any computer anywhere.
That is when the surprise outage is born — Friday 17:42, two hours after the “clean” merge, when the new code path meets the old path for the first time in production.
No amount of “LGTM” changes physics.
2. MR workflows are the opposite of Continuous Integration
Continuous Integration (the real 1999 XP definition) = integrate and fully verify the whole system many times per day so the delta is trivial.
Typical MR workflow:
branch lives 4 hours to 4 days
sits in review queue for another 4–48 hours
gets merged in a 600-line batch on Friday afternoon
That is batch-and-queue deferred integration — the very thing CI was invented to kill.
3. MR reviews are not design reviews (and never will be)
A real design review needs:
shared context
hours of immersion
whiteboarding alternatives
psychological safety to say “this whole approach is wrong”
An MR gives the reviewer:
a wall of diff
7–40 minutes
none of the context the author built up over two days
Therefore the only feedback possible is shallow: formatting, null checks, naming, tiny refactor suggestions.
Everything important (aggregate boundaries, invariant placement, future evolution, performance characteristics) stays unreviewed — not because it’s good, but because nobody has the context to challenge it.
4. MRs turn developers into isolated islands
Good design is a contact sport.
It happens live: pairing, mobbing, overhearing half a conversation and jumping in, pulling the product owner into a discussion.
MR culture enforces the opposite:
Disappear for two days
Come back with a “finished” artefact
Hope someone is willing to tell you it sucks
By the time the MR is opened, the design is socially frozen. Collaboration is dead.
Result: ten slightly different ways of doing the same thing, micro-architectures everywhere, drift nobody sees until the rewrite.
5. The illusion of safety is the worst part
Once someone clicks Approve, responsibility magically evaporates.
The author feels safer
The reviewer feels they “did due diligence”
Management gets a nice “98% reviewed” metric
We built a theatre of safety, not actual safety.
6. What a healthy flow actually looks like — a normal Thursday on a team that dropped mandatory MRs two years ago
09:15 – Stand-up (still exists, still 12 minutes).
Anna: “Today I’m tackling the overdue-fine change for customers that bill daily instead of weekly.”
09:20 – She pings the two people who know the billing corner best: “Got 10 minutes to think with me?”
They screenshare, inspect the Fine calculation code, and agree the cleanest place for the new rule is inside the Fine constructor itself.
09:35 – Anna pulls latest main, writes ~60 lines, runs the full app locally, clicks through every overdue scenario she can think of.
Everything feels right.
09:52 – She commits straight to main. No branch, no MR.
CI runs on the actual merged code, finishes green at 10:06.
12:47 – Dogfooding on staging.
Anna notices daily-billing customers show the old amount after a manual waiver — a cache invalidation gap.
13:30 – She pairs briefly, adds a single line to invalidate the right key, commits another 11-line change to main.
Tomorrow morning’s staging deploy will pick it up; if nobody screams, it goes to production.
No ticket
No review queue
No “please review my PR is blocking me”
No surprise outage because two branches met for the first time in prod
Just eight people working on the same code, talking early, verifying real behaviour, deploying daily.
That team shipped 19 production changes that week.
Exactly zero required a hotfix.
Field Guide: Symptoms You Are Living in the Cargo Cult Right Now
Tick the ones you saw last week:
A 1,200-line “refactor” PR open for five days with 47 comments about brace placement
Reviewer writes “LGTM” after eight minutes on a 600-line diff
Friday 16:55 merge → Monday morning hotfix
Architect is the bottleneck because every PR needs their sign-off
Someone posts “Can I get a quick review? I’m blocked” at least twice a day
Half the comments are “nit: move this to a new line” or “rename this variable”
Incident post-mortem ends with “it worked on my branch”
Team proudly reports “99.3% of code reviewed” while cycle time is 3.8 days
If you ticked three or more: full-blown cargo cult.
If You Can’t Burn It All Down Tomorrow — Three Levers You Can Pull Monday Morning
Most teams can’t flip to pure trunk-based overnight. Fine. Start moving the needle.
1. Ruthlessly enforce tiny changes
Rule: any change > 200 LOC is closed and must be split.
Effect: reviewers (if you keep them) can actually understand the diff; integration risk drops; queue moves faster.
2. Run your pipeline on the merge result, not the branch
GitHub merge queue, GitLab merged-results pipeline, Bitbucket merge checks — enable them.
Now the tests run on the actual code that will ship, not a hypothetical branch state.
3. Require upfront design alignment for anything >1 day
A 15-minute live design sync or tiny written mini-RFC before coding starts.
Effect: the important discussion happens early, when it’s cheap.
Pull these three and, even with MRs, you’ve killed 80% of the harm.
Most teams that do this quietly drop mandatory reviews within six months.
Conclusion
Merge Requests became the default because most teams lack the prerequisites for real trust:
a coherent domain model
real-time collaboration
tiny integration steps
strong verification of merged code
The MR ritual feels like it solves those problems.
It actually prevents them from being solved.
Stop treating symptoms.
Fix the disease.
Appendix: The Five Standard Objections and Why They Collapse Under Pure Logic
Objection 1 — “We’re remote. We can’t pair or mob all the time.”
MRs do not create shared understanding — they hide the lack of it.
If synchrony is hard, invest in deliberate synchrony, not async shallow review.
Objection 2 — “Large codebase, lots of coupling. We need cross-team sign-off.”
A 20-minute glance at a large diff cannot detect subtle coupling breaks.
Only tiny changes or upfront collaboration can.
Objection 3 — “Reviews are how juniors learn the codebase.”
Diff archaeology is the slowest onboarding method ever invented.
A clean domain model teaches a newcomer the system in under an hour.
Sprawling procedural code takes weeks.
Objection 4 — “We need an audit trail of who approved what.”
Git already records who wrote and merged.
Most PRs are deleted after merge anyway.
If compliance truly needs sign-off, build a tiny tool — don’t make 200 engineers pay with their velocity.
Objection 5 — “Without reviews we’d ship tons of stupid bugs.”
Shallow bugs are cheap to fix in a real environment with real observability.
The expensive bugs are deep design flaws — and MRs consistently miss those.
None of the defences survive five minutes of honest reasoning.
The cargo cult is strong because it feels safe and looks professional.
But it is still a cult.
Time to walk off the island.
Top comments (6)
It seems they adopted trunk-based development in your story. The negative for me with that way of working are feature flags. Feature flags create forks. If you have a complicated system, adding more forks can lead to code that creates unexpected side effects. And those side effects are not always as easy to fix as you might think.
Creating problems in production code because of a version control workflow feels like a none starter for me.
I rather have a branch that is synced with the main branch when working on bigger features.
Branching does not mean people work with different code, it just isolates the code changes.
When multiple people work with the same code, there is always a chance people change code others will need to change as well. For example when two people need to do changes in the same domain.
Trunk-based development doesn't prohibit branching, it wants them to have a short live span. And I agree with that part of the concept.
The truth is development is messy. I have worked on projects where the owners forgot about changes regularly, so there were branches that were months old before they were added.
They were added with no drama, because before they were added they were synced with the main branch, checked for merge conflicts and adjusted when needed.
I don't want to know what feature flag horror would happen with those projects. Feature flags should only last as long as the development phase.
That feels a lot like a metric that is forced upon a team by management.
Metrics never solved a problem, they are at most an indication. There are developers that love gaming metrics.
I did a little bit of ranting about trunk-based development in this comment, but I think you hit mark by stating the pull/merge requests should not be a ritual.
I think they are a good tool if you are not sure that the changes you made are the right solution. Communication and understanding leads the way, but that doesn't mean the end result is what it should be.
There is also something called developer blindness, and that can happen even if you are at the top of your game.
Ditch the ritual, keep the tool.
Thanks again for the reply!
One last thought, because you made me realise how perfectly history rhymes.
Continuous Integration was invented in the late 90s / early 2000s specifically to end the nightmare of long-lived branches, forgotten feature branches, painful integration phases, and “it-worked-on-my-machine surprises.
We paid for that lesson in blood, sweat and tears once, fixed it with CI, then GitHub/GitLab gave us beautiful Merge Request UIs and within ten years the industry happily re-invented the exact same hell — just with emojis and “LGTM”.
Branching isn’t free. Every branch is a debt you will pay at merge time.
The only times I personally accept a branch longer than a day are the rare, obvious exceptions:
a multi-week pure visual redesign
a big-bang entity refactoring
Everything else (99 % of real work) goes straight to main the same day, after a 5–10 minute conversation with whoever knows the area. No feature flags, no review queues, no long-lived feature branches.
In 25 years I have never seen the kind of chaos you describe (months-old branches, flag explosions, dramatic merges) on a team that kept changes tiny and main as the single source of truth.
Development only feels inherently messy when we choose workflows that make it messy.
Remove the ceremony, keep the discipline, and the mess vanishes.
Appreciate the pushback
There is always one branch the source of truth, whether branching is used or not.
The tiny changes are not the problem. It is the sum of their parts that can go haywire.
I guess you picked the clients that understand software development. I worked for all sorts of people, so sometimes I had to battle with biases about software development.
Sometimes I liked it because it gave me another perspective, and other times it drained my energy.
Totally agree that there is always one branch that is the source of truth.
The difference is only this:
In a healthy team that branch is main and it is updated dozens of times per day.
There is no alternative.
In an unhealthy team that branch is called develop, release-2025.2, or “the integration branch” and it is updated every few weeks after a merge ceremony.
Whenever I enter a new team that works with MRs I get them to work from main only as soon as possible. After a while we look at the numbers and the feeling in the team, and usually no one wants to revert. Discussion improves, merge conflicts disappear, shared understanding skyrockets — so who would want to go back, really?
I never understood this misuse of a versioning system. Git is for versioning — keeping history safe and tagging releases. Using the versioning system for branches + MRs as the daily development process is like eating soup with a fork. It “works” to some degree, but it’s painfully inefficient.
Thanks again for the great exchange!
Sure I have seen cases like that, and I agree it is not a good workflow.
I'm a bit offended by the term unhealthy team. Inexperienced, lacking knowledge would be a better characterization.
The point I'm trying to make is that MRs and branching are tools version control systems provide. And they should be used when it is needed.
If a language adds the goto keyword, you only going to use it when it is the best solution, it should not replace the common forking methods.
I'm curious why you don't mention feature flags? Don't they get used in the projects you worked on? Or don't you have any development experience using them?
No offence intended with “unhealthy”; I just meant “a workflow that actively harms velocity and quality”, not the people. Poor choice of word.
Feature flags: I don’t use them. Ever. And I think I would never use them. I would not modify production code with a switch on whether to enable/disable functionality.
What you checkin should either work or not interfere with normal operations of the application.
I’ve honestly never needed anything more clever than the basics:
new domain part → it sits empty or unpopulated until it’s ready
new UI parts → menu item / button stays hidden (commented out - super explicit)
No runtime switches, no config, no flag litter. It’s always been enough.
MRs are a tool that GitLab/GitHub are happy to sell you, but giving someone a Ferrari doesn’t make it the right tool to plow a field.
Thanks again for the great discussion — really enjoyed it!