Are pull requests good for your team?

I’ve been working in the software industry long enough to have seen different ways of contributing to a codebase. And after twelve years working with pull requests, their current adoption as a standard doesn’t really convince me. But to explain why, I first need to share a bit of history.

Collaboration over tooling

When I was still at university (2001-2007), almost all practical assignments were done in pairs or small groups. We sat around one or two computers, took turns writing code and solved problems together. These were my first baby steps doing pair programming and mob programming, years before I knew those practices had a name.

When an assignment was large enough, we would split up for a while and regroup every couple of hours to integrate our work, especially when there were dependencies. We didn’t use a version control system (VCS)—Subversion had been released only recently and Git didn’t even exist—so our code had no versioning and we merged everything manually.

Around 2005 we started using Subversion. After reading about it, I suggested we give it a try, and my classmates quickly embraced it, as it removed much of the manual work involved in versioning and merging. Other than that, little else changed. In fact, although we worked with branches, we still merged directly into “trunk” (the main branch in Subversion’s terminology) and resolved conflicts together whenever necessary.

The same was true during my first professional job in 2007. We also used Subversion and often programmed in pairs. Not as frequently as I had done in university, but enough not to be considered unusual. Changes were still merged directly into the main branch and any conflicts were resolved locally. If necessary, you would ask a colleague to sort them out together. We simply treated integration as part of everyday programming rather than a separate activity.

Later in 2010, I discovered Git. What impressed me most about it was its performance, particularly when managing branches. So I offered to lead the migration from Subversion to Git in the company where I was working, and within a few months Subversion was a thing of the past.

Apart from faster branching, and more powerful capabilities in general, Git didn’t change much about how we worked either. We still collaborated closely and merged directly into the main branch. To minimise conflicts, we would pull the latest state and push our contributions multiple times a day. We even used to tease anyone who finished the day without having merged their work.

Some managers occasionally questioned why two developers were doing one person’s work. To my eyes, this only revealed that they didn’t have much understanding of the economics of software development. But luckily, such comments never led to any concrete action.

Looking back now, we were practising a form of what was known as continuous integration (a.k.a. trunk-based development), even though we didn’t have a name for it.

From collaboration to code review

Years passed and in 2014 I moved to Switzerland to start a new job where my team told me that they used pull requests to collaborate.

This is a feature that GitHub had first added in February 2008, inspired by the collaboration model in open-source projects, and then launched in August 2010 in a revamped version closer to what we know today (see A Brief History of the Pull Request for more details).

The pull request workflow is relatively simple, unless you go crazy with something like GitFlow. Changes are implemented on branches, submitted as a “request to pull” them, reviewed by at least one core member of the project and merged into the main branch once approved.

This approach makes sense in the context of open-source. It makes it easy for people outside the core team to contribute and for the core team to vet changes, suggest improvements and eventually integrate the contribution. Before, all that had to happen by submitting patches to a mailing list. Pull requests simplified the lives of maintainers and external contributors.

Until that moment, I had only used them precisely for that, with my first contribution being as early as April 2011 and some more after that one. This new job in Switzerland was the first time when I saw them used for closed-source projects developed by an in-house small team.

But considering where I was coming from, that level of “protectionism” didn’t feel right. In a small healthy team who works together closely, where members trust each other and there’s collective ownership of the product, this loss of autonomy to make changes and the increased focus on code reviews over collaborative programming was foreign to me. Collaboration had become something that happened after the code was written, instead of while it was being written.

I committed to it anyway because I assumed that, as a developer who had just left a small island in the middle of the Atlantic to grow professionally in a more prosperous country, I didn’t know any better. Pull requests were more modern, so they had to be the way forward, right?

The hidden incentives of pull requests

Jump to 2026, and I’m now convinced that they’re mostly counterproductive for teams not involved in open-source projects. Whenever I consider all pros and cons, the net balance is usually negative.

But somehow many developers today have come to treat them as a panacea, struggling to imagine that other models of collaboration can be equally or even more effective.

So let me give you six ways in which I think pull requests can be harmful, based on what I’ve observed in almost 20 years of programming experience.

Longer feedback loops

First, they increase the time between the moment we start working on something until we see it deployed. Every change is delayed because they have to wait in a queue for review before it can be integrated.

This makes feedback loops longer, something that research done by the DORA Research Program (among others) and published in the State of DevOps reports and Accelerate advises against.

More isolated development

Second, they encourage working alone until a change is “ready for review”, which optimises for individual throughput instead of solving problems together. This is problematic in two partially related ways.

On one hand, it makes it easier to remain stuck with a problem for too long before reaching out for help. What’s worse, we run the risk of spending too much time solving the wrong problem or applying the wrong solution.

In theory, nothing stops us from teaming up if we need to. In practice, it requires a disciplined, proactive decision, since the pull request workflow doesn’t naturally lead to it.

On the other hand, it reinforces a misguided view of development where work is split into independent tasks so that everyone can stay busy. It’s the same reasoning behind the criticism that pair programming is “two developers doing one person’s work”: if everyone works on their own task, more work gets done.

The problem is that software development organisations are not assembly lines. Most of the effort goes into understanding the problem, validating assumptions, and adapting when we discover we were wrong. Those activities benefit from collaboration rather than isolation.

Successful organisations ultimately care about teams that can figure out and deliver the right business outcomes sooner, not by how many pull requests they merge or tickets they close. Ironically, optimising for individual throughput risks making the whole team slower at figuring out and delivering the right solution.

Larger and more complex deployments

Third, they lead to multiple changes being deployed at once, which then increases the risk of failure.

Ideally, we should prefer many and more frequent small changes because they’re easier to understand, predict and fix when something goes wrong. But doing so by opening multiple pull requests comes with a non-negligible overhead:

  • Contributors have to orchestrate them (e.g. with stacked diffs). As much of an expert on rebasing we might be, this is still cumbersome and prone to error
  • Reviewers have a harder time following the overall picture, specially when it’s not the same person each time
  • More time passes in between deployments, which again makes feedback loops longer and can complicate recovering from failure

So what happens in practice is that we create bigger pull requests so that we don’t have to bother with all these ceremonies and delays. As a result, we end up with a change that takes longer to review, needs more thorough testing and risks a bigger failure after deployment.

Less incremental improvements

Fourth, and because of the previous one, preparatory work and gardening becomes harder to justify. As Kent Beck so wisely said:

For each desired change, make the change easy (warning: this may be hard), then make the easy change

“Making the change easy” normally requires a series of smaller preparatory changes before we can implement what we actually want. And while we could do them in separate pull requests, it takes longer and renders the overall experience needlessly complex, as we just saw before.

Think about it this way: do tiny improvements like renaming a variable or reorganising private methods in a class warrant the whole ceremony of a pull request? Probably not. But because everything has to go through pull requests, we either put improvements and functionality changes together to avoid the hassle, or don’t do any improvements at all.

Using code inspection as a quality gate

Fifth, they promote treating manual code inspection as an effective way to prevent defects. Sure, we may certainly be able to find some bugs by inspecting code. However, automated and exploratory tests are far better and more effective tools for this purpose.

Moreover, the aforementioned reports from the DORA Research Program consistently show that being able to recover fast from a failure often leads to a better delivery performance and quality than trying to catch defects at review time.

This might sound like advocating for failure, and in a certain way it is. But not because defects and low quality (call it technical debt if you want) are acceptable. Rather, because they’re sometimes inevitable. And evidence strongly suggests that being good at detecting and solving problems is more effective than being good at reviewing code to prevent them up front.

Weaker engineering judgement

Sixth, they don’t tend to foster code ownership nor growing our expertise. If every decision ultimately needs someone else’s approval, we might gradually optimise for getting reviews accepted rather than developing confidence in our own engineering judgement.

Imagine you’re the only developer in a small organisation. Who reviews your code? How do you know you have chosen the right design? And even if you have a decent test coverage, how can you be certain you’re not introducing subtle bugs? You can’t really know until you ship it and continue iterating.

So to minimise risks, you naturally start to deploy in small increments. And when the time comes, and you ship a defect accidentally, you learn how to detect it and fix it fast. Over time, you become better at anticipating what can go wrong, because owning your mistakes is part of owning the software.

But when we always depend on someone else to review what we’ve done, we have fewer opportunities to make our own decisions, take responsibility for their consequences, and nurture our engineering judgement.

Returning to trust-based collaboration

In retrospect, one of the most significant changes to the way we’ve been building software over the last twenty years has been the shift from collaborating during development to reviewing the code afterwards. From synchronous, trust-based integration to an asynchronous, approval-based one. This is the crux of the matter.

I also believe that the widespread adoption of pull requests has played a big role in that. Their problems don’t necessarily manifest all at once nor affect all teams to the same degree. And you might be willing to accept some drawbacks if there’s an upside somewhere else. But we have to do an honest exercise when assessing whether they truly help us achieve the best possible delivery performance in our teams.

If I could change one thing, it would be going back to a model where we collaborate earlier, integrate continuously and trust each other enough that code review becomes, once again, a learning tool rather than a gate. This approach has been well described for decades, and my own experience has reinforced my belief that it works.