6 min read

Stacked PRs: the ultimate coding productivity hack

What are stacked PRs, and how can it make you a more productive software engineer?
Stacked PRs: the ultimate coding productivity hack

It’s widely accepted today that software engineers are most productive when they’re in a “flow state”. You might not call it that, but you’d likely recognise it. You’re making connections quicker, the code is flying out of your fingertips, and you basically just feel like you’re moving fast. It’s a great feeling, and when you’re in that state, you absolutely want to maintain it.

Nothing breaks flow state like having to stop and ask a teammate for a code review. You know code review makes for better code, so you want it, but if you keep going, you’re going to have so much code it will become cumbersome to review effectively. So what do you do?

Enter Stacked Pull Requests. Stacked PRs doesn‘t mandate a particular technical tool, but a way of breaking up your code into small discrete units. Below I’ll describe it in very GitHub centric language, but most source control tooling could easily handle it.

Branching Out

In a traditional GitHub environment there’s two common approaches to code review. Branching off main frequently, and long lived feature branches.

The branch off main approach

In the branch off main strategy shown above, you branch off main, write your code, open a PR and merge back into main. The benefit is in the simplicity. You’re only ever a single hop from main, branches are very short lived so you rarely encounter rebasing issues, and your branch doesn’t build up too much risk.

Branching off main

Let’s imagine a pretty common scenario - adding a new feature to your product. Maybe it has a backend piece, a front end piece, and perhaps some kind of infra piece. It’s a small feature, so you’re going to build it solo. If you share all three things in a single PR, you could be talking 1000s of lines of code, which is essentially impossible to meaningfully review.

So being the considerate engineer you are, you decide to sequence the work. You create a branch off main, build the backend piece, push it and open a PR to merge it back to main. But what now? You’re blocked until someone reviews it. You’ve broken your flow.

The long lived feature branch approach

The other strategy is a long lived feature branch. At the start of development, you branch off main, and you essentially create a parallel branch (let’s call it feature-xyz, that you’re constantly re-branching and merging to.

Long lived feature branches

When it comes to reviews, you have two strategies. One, you review when it merges from some-branch-off-feature-xyz into feature-xyz, and skip reviews on the merge from feature-xyz into main, or two, you do one big review when you merge into main.

In the first strategy, you’re still breaking your flow, you’ve just moved the problem to a different branch. In the second one, you keeping your flow, but at the cost of a difficult code review.

So what’s stacking then?

Stacking, as I use it, is probably best thought as a mix of the two.

Here’s the workflow I would use to build the feature above. I’ll open the codebase on main, and start writing code (yes, that’s write, I start in main without a branch). Once I’ve written the smallest amount of code possible, I create a new branch (let’s call it feature-xyz-a)and commit those changes to the new branch.

This is an important detail. When I say “the smallest amount of code possible”, what I mean is a change that I can safely merge to main as is. It doesn’t have to be particularly useful, but it shouldn’t break anything, and it should be trivial to review. I’m usually aiming for a change of less than 100 lines. If it’s an API, I might create the endpoint, but have it return an empty response.

At this point, I’ll open a pull request from my branch into main. And this is where the “stacking” comes in. Instead of waiting for a review, I’ll keep coding and once I have another logical and isolated change, I’ll create a new branch as a child branch of feature-xyz-a, let’s call it feature-xyz-b, then I’ll open a PR for that targeting feature-xyz-a. This is the next branch in the stack.

The stacking workflow

By this time, maybe I’ve gotten some feedback on feature-xyz-a. In that case, I’ll swap to that branch, make a change, amend the commit and push it. Then I’ll switch back to feature-xyz-b, rebase it on top of feature-xyz-a and continue build the next logical step.

The other possibility is that the PR for feature-xyz-a has been approved, and because I built it to be safe to merge, I can merge it and rebase feature-xyz-b on top of main.

When you’re ready to merge, you have two options.

(1) Merge your branches into main as you go, rebasing as I describe above. This is my preferred approach

(2) “Collapse” the branch. Merge the descending branches upwards into the parent branch until you get to main

Isn’t this just a feature branch?

Yeah, kind of, but because we’re building each branch as something we can merge trivially into the parent of it, we’re minimising the risk associated with a single large PR landing onto the main branch. What you need to maintain is the order of the merging, but it’s a lot easier than it sounds.

The biggest downside to all this branch is the constant rebasing, but thankfully there’s a myriad of tooling available to mitigate this.

Ok, time to commit…

You‘ve probably gotten to this point and still aren’t 100% clear or convinced of the benefits of this approach. That’s fine, and honestly somewhat expected. The only real way to see the benefit is to use it. I promise after a week or two it’ll just click.

And that’t the beauty of stacking. Because it’s all just branches under the hood, you don’t actually need your whole team to buy into it. So trust me, just give it a chance, and you’ll never look back.


Further Reading

For more on stacking, check these out -

Articles

Some tools which make the workflow easier

  • Graphite CLI: Part of a broader suite of paid tooling, this free CLI can handle the creation of branches, the frequent rebasing.
  • Git-town: A high-level CLI for git. Includes support for opening and syncing branches to GitHub.
  • GitLab CLI: For people who use GitLab, they recently updated their CLI to support stacking